zhentao 3 days ago
commit
4503be9de1
100 changed files with 6717 additions and 0 deletions
  1. 33 0
      .gitignore
  2. 207 0
      pom.xml
  3. 137 0
      src/main/java/com/zhentao/Ai/advertisement/DeepSeekWebSocketHandler.java
  4. 24 0
      src/main/java/com/zhentao/Ai/config/WebSocketConfig.java
  5. 21 0
      src/main/java/com/zhentao/Ai/dto/DeeseekRequest.java
  6. 28 0
      src/main/java/com/zhentao/ImApplication.java
  7. 34 0
      src/main/java/com/zhentao/config/CorsConfig.java
  8. 35 0
      src/main/java/com/zhentao/config/JacksonConfig.java
  9. 35 0
      src/main/java/com/zhentao/config/MailConfig.java
  10. 11 0
      src/main/java/com/zhentao/config/NullLogin.java
  11. 29 0
      src/main/java/com/zhentao/config/RedisConfig.java
  12. 43 0
      src/main/java/com/zhentao/config/RedissonConfig.java
  13. 19 0
      src/main/java/com/zhentao/config/interceptorconfig.java
  14. 29 0
      src/main/java/com/zhentao/enums/ApiServerException.java
  15. 6 0
      src/main/java/com/zhentao/enums/BaseExceptionEnum.java
  16. 15 0
      src/main/java/com/zhentao/exception/AsynException.java
  17. 55 0
      src/main/java/com/zhentao/exception/GlobalExceptionHandler.java
  18. 49 0
      src/main/java/com/zhentao/groups/MongoDB/controller/GroupMessageController.java
  19. 47 0
      src/main/java/com/zhentao/groups/MongoDB/pojo/GroupMessage.java
  20. 47 0
      src/main/java/com/zhentao/groups/MongoDB/pojo/Message.java
  21. 88 0
      src/main/java/com/zhentao/groups/controller/groupsController.java
  22. 23 0
      src/main/java/com/zhentao/groups/dto/AddGroupMembers.java
  23. 39 0
      src/main/java/com/zhentao/groups/dto/AddGroupsDto.java
  24. 14 0
      src/main/java/com/zhentao/groups/dto/AddUserLoginDto.java
  25. 23 0
      src/main/java/com/zhentao/groups/dto/DelGroupMembers.java
  26. 11 0
      src/main/java/com/zhentao/groups/dto/GroupDto.java
  27. 10 0
      src/main/java/com/zhentao/groups/dto/OutGroupDto.java
  28. 19 0
      src/main/java/com/zhentao/groups/dto/OutGroupsDto.java
  29. 13 0
      src/main/java/com/zhentao/groups/dto/UpdateGroupMemberRoleDto.java
  30. 20 0
      src/main/java/com/zhentao/groups/mapper/GroupMembersMapper.java
  31. 20 0
      src/main/java/com/zhentao/groups/mapper/GroupsMapper.java
  32. 116 0
      src/main/java/com/zhentao/groups/pojo/GroupMembers.java
  33. 139 0
      src/main/java/com/zhentao/groups/pojo/Groupss.java
  34. 13 0
      src/main/java/com/zhentao/groups/service/GroupMembersService.java
  35. 54 0
      src/main/java/com/zhentao/groups/service/GroupsService.java
  36. 22 0
      src/main/java/com/zhentao/groups/service/impl/GroupMembersServiceImpl.java
  37. 444 0
      src/main/java/com/zhentao/groups/service/impl/GroupsServiceImpl.java
  38. 69 0
      src/main/java/com/zhentao/groups/vo/GroupsVo.java
  39. 19 0
      src/main/java/com/zhentao/groups/vo/UserVo.java
  40. 40 0
      src/main/java/com/zhentao/handler/PointTypeHandler.java
  41. 88 0
      src/main/java/com/zhentao/information/cache/ChannelCache.java
  42. 73 0
      src/main/java/com/zhentao/information/cache/GroupChannelCache.java
  43. 97 0
      src/main/java/com/zhentao/information/cache/GroupMemberCache.java
  44. 99 0
      src/main/java/com/zhentao/information/config/NettyConfig.java
  45. 42 0
      src/main/java/com/zhentao/information/config/WebSocketHandshakeInterceptor.java
  46. 37 0
      src/main/java/com/zhentao/information/controller/FileController.java
  47. 109 0
      src/main/java/com/zhentao/information/controller/MessageController.java
  48. 48 0
      src/main/java/com/zhentao/information/entity/ChatMessage.java
  49. 29 0
      src/main/java/com/zhentao/information/entity/Message.java
  50. 76 0
      src/main/java/com/zhentao/information/handler/HeartbeatHandler.java
  51. 189 0
      src/main/java/com/zhentao/information/handler/WebSocketHandler.java
  52. 55 0
      src/main/java/com/zhentao/information/netty/NettyServer.java
  53. 29 0
      src/main/java/com/zhentao/information/repository/ChatMessageRepository.java
  54. 57 0
      src/main/java/com/zhentao/information/service/GroupInitService.java
  55. 347 0
      src/main/java/com/zhentao/information/service/WebSocketService.java
  56. 47 0
      src/main/java/com/zhentao/intercepoter/Userinterceptor.java
  57. 93 0
      src/main/java/com/zhentao/moment/controller/MonmentController.java
  58. 113 0
      src/main/java/com/zhentao/moment/domain/MomentComments.java
  59. 91 0
      src/main/java/com/zhentao/moment/domain/MomentLikes.java
  60. 99 0
      src/main/java/com/zhentao/moment/domain/MomentMedias.java
  61. 127 0
      src/main/java/com/zhentao/moment/domain/UserMoments.java
  62. 20 0
      src/main/java/com/zhentao/moment/dto/CommentsDto.java
  63. 24 0
      src/main/java/com/zhentao/moment/dto/MonmentDto.java
  64. 41 0
      src/main/java/com/zhentao/moment/enums/ContentTypeEnum.java
  65. 18 0
      src/main/java/com/zhentao/moment/mapper/MomentCommentsMapper.java
  66. 18 0
      src/main/java/com/zhentao/moment/mapper/MomentLikesMapper.java
  67. 18 0
      src/main/java/com/zhentao/moment/mapper/MomentMediasMapper.java
  68. 18 0
      src/main/java/com/zhentao/moment/mapper/UserMomentsMapper.java
  69. 13 0
      src/main/java/com/zhentao/moment/service/MomentCommentsService.java
  70. 13 0
      src/main/java/com/zhentao/moment/service/MomentLikesService.java
  71. 13 0
      src/main/java/com/zhentao/moment/service/MomentMediasService.java
  72. 42 0
      src/main/java/com/zhentao/moment/service/UserMomentsService.java
  73. 22 0
      src/main/java/com/zhentao/moment/service/impl/MomentCommentsServiceImpl.java
  74. 22 0
      src/main/java/com/zhentao/moment/service/impl/MomentLikesServiceImpl.java
  75. 22 0
      src/main/java/com/zhentao/moment/service/impl/MomentMediasServiceImpl.java
  76. 772 0
      src/main/java/com/zhentao/moment/service/impl/UserMomentsServiceImpl.java
  77. 77 0
      src/main/java/com/zhentao/osspicture/OssConfig.java
  78. 231 0
      src/main/java/com/zhentao/osspicture/OssUtil.java
  79. 110 0
      src/main/java/com/zhentao/osspicture/ossController.java
  80. 34 0
      src/main/java/com/zhentao/shouye/controller/UserShouyeController.java
  81. 72 0
      src/main/java/com/zhentao/shouye/domain/Groups.java
  82. 55 0
      src/main/java/com/zhentao/shouye/domain/UserShouye.java
  83. 9 0
      src/main/java/com/zhentao/shouye/dto/UserShouyeDto.java
  84. 20 0
      src/main/java/com/zhentao/shouye/mapper/UserShouyeMapper.java
  85. 16 0
      src/main/java/com/zhentao/shouye/service/UserShouyeService.java
  86. 95 0
      src/main/java/com/zhentao/shouye/service/impl/UserShouyeServiceImpl.java
  87. 94 0
      src/main/java/com/zhentao/tool/TokenUtils.java
  88. 33 0
      src/main/java/com/zhentao/user/controller/LocationController.java
  89. 60 0
      src/main/java/com/zhentao/user/controller/QrCodeController.java
  90. 193 0
      src/main/java/com/zhentao/user/controller/UserController.java
  91. 79 0
      src/main/java/com/zhentao/user/domain/UserLocation.java
  92. 158 0
      src/main/java/com/zhentao/user/domain/UserLogin.java
  93. 101 0
      src/main/java/com/zhentao/user/domain/UserOnlineStatus.java
  94. 17 0
      src/main/java/com/zhentao/user/dto/EmailDto.java
  95. 16 0
      src/main/java/com/zhentao/user/dto/ForgetPassDto.java
  96. 53 0
      src/main/java/com/zhentao/user/dto/FriendDto.java
  97. 13 0
      src/main/java/com/zhentao/user/dto/HeartbeatDto.java
  98. 15 0
      src/main/java/com/zhentao/user/dto/LocationDto.java
  99. 26 0
      src/main/java/com/zhentao/user/dto/NearbyUserDTO.java
  100. 19 0
      src/main/java/com/zhentao/user/dto/NoteDto.java

+ 33 - 0
.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 207 - 0
pom.xml

@@ -0,0 +1,207 @@
+<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.zhentao</groupId>
+    <artifactId>IM</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>IM</name>
+    <description>IM</description>
+    <properties>
+        <java.version>1.8</java.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <spring-boot.version>2.6.13</spring-boot.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>dashscope-sdk-java</artifactId>
+            <version>2.18.2</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-simple</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>core</artifactId>
+            <version>3.5.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+            <version>3.5.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>3.17.4</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.xml.bind</groupId>
+            <artifactId>jaxb-api</artifactId>
+            <version>2.3.1</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.activation</groupId>
+            <artifactId>activation</artifactId>
+            <version>1.1.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.hibernate.validator</groupId>
+            <artifactId>hibernate-validator</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson</artifactId>
+            <version>3.17.3</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.16</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>3.5.4</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+            <version>4.1.86.Final</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.83</version>
+        </dependency>
+
+        <!-- MySQL依赖 -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-mail</artifactId>
+        </dependency>
+        <!-- Spring Data JPA -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+
+        <!-- MongoDB依赖 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-mongodb</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>3.15.1</version> <!-- 使用最新稳定版本 -->
+        </dependency>
+<!--二维码-->
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>core</artifactId>
+            <version>3.5.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+            <version>3.5.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.belerweb</groupId>
+            <artifactId>pinyin4j</artifactId>
+            <version>2.5.0</version>
+        </dependency>
+    </dependencies>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                    <encoding>UTF-8</encoding>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${spring-boot.version}</version>
+                <configuration>
+                    <mainClass>com.zhentao.ImApplication</mainClass>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>repackage</id>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 137 - 0
src/main/java/com/zhentao/Ai/advertisement/DeepSeekWebSocketHandler.java

@@ -0,0 +1,137 @@
+package com.zhentao.Ai.advertisement;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.zhentao.Ai.dto.DeeseekRequest;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
+import org.springframework.web.socket.handler.TextWebSocketHandler;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class DeepSeekWebSocketHandler extends TextWebSocketHandler {
+
+    private static final Gson gson = new Gson();
+    private static final String DEEPSEEK_API_URL = "https://api.deepseek.com/chat/completions";
+    private static final String API_KEY = "sk-df51dab7323441998d41f18494098ddc"; // 替换为你的API密钥
+
+        // ...其他成员变量...
+
+    @Override
+    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
+        String question = message.getPayload();
+        if (question == null || question.trim().isEmpty()) {
+            sendErrorMessage(session, "问题不能为空");
+            return;
+        }
+
+        // 使用线程池管理请求(避免频繁创建线程)
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        executor.submit(() -> processStreamingResponse(session, question));
+        executor.shutdown();
+    }
+
+    private void processStreamingResponse(WebSocketSession session, String question) {
+        HttpURLConnection connection = null;
+        try {
+            URL url = new URL(DEEPSEEK_API_URL);
+            connection = (HttpURLConnection) url.openConnection();
+            connection.setRequestMethod("POST");
+            connection.setRequestProperty("Content-Type", "application/json");
+            connection.setRequestProperty("Authorization", "Bearer " + API_KEY);
+            connection.setRequestProperty("Accept", "text/event-stream");
+            connection.setDoOutput(true);
+            connection.setDoInput(true);
+            connection.setUseCaches(false);
+            connection.setConnectTimeout(5000); // 5秒连接超时
+            connection.setReadTimeout(30000);  // 30秒读取超时
+
+            // 发送请求体
+            try (OutputStream os = connection.getOutputStream()) {
+                os.write(buildRequestBody(question).getBytes(StandardCharsets.UTF_8));
+                os.flush();
+            }
+
+            // 处理流式响应
+            try (BufferedReader reader = new BufferedReader(
+                    new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
+
+                String line;
+                while ((line = reader.readLine()) != null && session.isOpen()) {
+                    if (line.startsWith("data: ") && !line.equals("data: [DONE]")) {
+                        String content = parseContent(line.substring(6));
+                        if (content != null) {
+                            session.sendMessage(new TextMessage(content));
+                        }
+                    }
+                }
+            }
+        } catch (SocketTimeoutException e) {
+            sendErrorMessage(session, "API响应超时,请重试");
+        } catch (IOException e) {
+            sendErrorMessage(session, "网络错误: " + e.getMessage());
+        } finally {
+            if (connection != null) connection.disconnect();
+        }
+    }
+
+        private String buildRequestBody(String question) {
+            List<DeeseekRequest.Message> messages = new ArrayList<>();
+            messages.add(DeeseekRequest.Message.builder()
+                    .role("system")
+                    .content("你是一个佳佳聊天小助手,请用中文回答")
+                    .build());
+            messages.add(DeeseekRequest.Message.builder()
+                    .role("user")
+                    .content(question)
+                    .build());
+
+            return gson.toJson(DeeseekRequest.builder()
+                    .model("deepseek-chat")
+                    .messages(messages)
+                    .stream(true)
+                    .build());
+        }
+
+        private String parseContent(String json) {
+            try {
+                JsonObject obj = JsonParser.parseString(json).getAsJsonObject();
+                if (obj.has("choices")) {
+                    JsonObject delta = obj.getAsJsonArray("choices")
+                            .get(0).getAsJsonObject()
+                            .getAsJsonObject("delta");
+                    if (delta.has("content")) {
+                        System.err.println(delta.get("content").getAsString());
+                        return delta.get("content").getAsString();
+                    }
+                }
+                return null;
+            } catch (Exception e) {
+                return null;
+            }
+        }
+
+        private void sendErrorMessage(WebSocketSession session, String message) {
+            try {
+                if (session.isOpen()) {
+                    session.sendMessage(new TextMessage(
+                            "{\"error\": \"" + message + "\"}"
+                    ));
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }

+ 24 - 0
src/main/java/com/zhentao/Ai/config/WebSocketConfig.java

@@ -0,0 +1,24 @@
+package com.zhentao.Ai.config;
+
+import com.zhentao.Ai.advertisement.DeepSeekWebSocketHandler;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.config.annotation.EnableWebSocket;
+import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
+import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
+import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
+import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
+
+// WebSocketConfig.java
+@Configuration
+@EnableWebSocket
+public class WebSocketConfig implements WebSocketConfigurer {
+
+    @Override
+    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
+        registry.addHandler(new DeepSeekWebSocketHandler(), "/ws")
+                .setAllowedOrigins("*")
+                .setHandshakeHandler(new DefaultHandshakeHandler())
+                .addInterceptors(new HttpSessionHandshakeInterceptor())
+                .setAllowedOriginPatterns("*"); // 更灵活的跨域控制
+    }
+}

+ 21 - 0
src/main/java/com/zhentao/Ai/dto/DeeseekRequest.java

@@ -0,0 +1,21 @@
+package com.zhentao.Ai.dto;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@Builder
+public class DeeseekRequest {
+    private String model;
+    private List<Message> messages;
+    private boolean stream;  // 关键:新增 stream 字段,控制是否流式输出
+
+    @Data
+    @Builder
+    public static class Message {
+        private String role;
+        private String content;
+    }
+}

+ 28 - 0
src/main/java/com/zhentao/ImApplication.java

@@ -0,0 +1,28 @@
+package com.zhentao;
+
+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.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@SpringBootApplication
+@MapperScan("com.zhentao.*.mapper")
+@EnableScheduling
+public class ImApplication {
+
+    public static void main(String[] args){
+        SpringApplication.run(ImApplication.class, args);
+    }
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
+        // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
+        return interceptor;
+    }
+
+}

+ 34 - 0
src/main/java/com/zhentao/config/CorsConfig.java

@@ -0,0 +1,34 @@
+package com.zhentao.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+
+@Configuration
+public class CorsConfig {
+
+    @Bean
+    public CorsFilter corsFilter() {
+        CorsConfiguration config = new CorsConfiguration();
+        // 允许特定的源
+        config.addAllowedOriginPattern("http://101.200.59.170:5173");
+        config.addAllowedOriginPattern("http://101.200.59.170:8081");
+        config.addAllowedOriginPattern("ws://101.200.59.170:8081");
+        config.addAllowedOriginPattern("ws://101.200.59.170:8081/ws");
+//        config.addAllowedOriginPattern("*");
+        // 允许所有HTTP方法
+        config.addAllowedMethod("*");
+        // 允许所有请求头
+        config.addAllowedHeader("*");
+        // 允许携带凭证(如cookies、认证头)
+        config.setAllowCredentials(true);
+
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        // 对所有接口都有效
+        source.registerCorsConfiguration("/**", config);
+
+        return new CorsFilter(source);
+    }
+}

+ 35 - 0
src/main/java/com/zhentao/config/JacksonConfig.java

@@ -0,0 +1,35 @@
+package com.zhentao.config;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+
+@Configuration
+public class JacksonConfig {
+
+    @Bean
+    public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
+        ObjectMapper objectMapper = builder.build();
+
+        SimpleModule module = new SimpleModule();
+        module.addSerializer(Long.class, new ToStringSerializer());
+        module.addSerializer(Long.TYPE, new ToStringSerializer());
+        objectMapper.registerModule(module);
+
+        return objectMapper;
+    }
+    @Bean
+    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
+        SimpleModule module = new SimpleModule();
+        // 全局配置Long和Long.TYPE序列化为字符串
+        module.addSerializer(Long.class, ToStringSerializer.instance);
+        module.addSerializer(Long.TYPE, ToStringSerializer.instance);
+
+        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
+        converter.getObjectMapper().registerModule(module);
+        return converter;
+    }
+}

+ 35 - 0
src/main/java/com/zhentao/config/MailConfig.java

@@ -0,0 +1,35 @@
+package com.zhentao.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.JavaMailSenderImpl;
+
+import java.util.Properties;
+
+@Configuration
+public class MailConfig {
+    private final String host = "smtp.126.com";
+    private final int port = 465; // 或587,根据加密方式
+    private final String username = "vx3181048507@126.com";
+    private final String password = "MYnUX38Qv75mUvwb"; // 126邮箱生成的授权码
+
+    @Bean
+    public JavaMailSender javaMailSender() {
+        JavaMailSenderImpl sender = new JavaMailSenderImpl();
+        sender.setHost(host);
+        sender.setPort(port);
+        sender.setUsername(username);
+        sender.setPassword(password);
+
+        Properties props = sender.getJavaMailProperties();
+        props.put("mail.smtp.auth", "true");
+        // 匹配端口的加密方式:
+        // - 465端口(SSL):props.put("mail.smtp.ssl.enable", "true");
+        props.put("mail.smtp.ssl.enable", "true"); // 关键:启用SSL
+
+        props.put("mail.debug", "true"); // 开启调试日志(关键!)
+
+        return sender;
+    }
+}

+ 11 - 0
src/main/java/com/zhentao/config/NullLogin.java

@@ -0,0 +1,11 @@
+package com.zhentao.config;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface NullLogin {
+}

+ 29 - 0
src/main/java/com/zhentao/config/RedisConfig.java

@@ -0,0 +1,29 @@
+package com.zhentao.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.StringRedisSerializer;
+
+@Configuration
+public class RedisConfig {
+
+    @Bean
+    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+        
+        // 设置key序列化方式
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setHashKeySerializer(new StringRedisSerializer());
+        
+        // 设置value序列化方式
+        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
+        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
+        
+        template.afterPropertiesSet();
+        return template;
+    }
+}

+ 43 - 0
src/main/java/com/zhentao/config/RedissonConfig.java

@@ -0,0 +1,43 @@
+package com.zhentao.config;
+
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.config.Config;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+// 定义这是一个Spring配置类,用于配置Redisson相关的Bean
+@Configuration
+public class RedissonConfig {
+//     从配置文件(如application.properties或application.yml)中获取Redis服务器的主机地址
+    @Value("${spring.redis.host}")
+    private String host;
+    // 从配置文件中获取Redis服务器的端口号
+    @Value("${spring.redis.port}")
+    private int port;
+    // 从配置文件中获取要使用的Redis数据库索引
+    @Value("${spring.redis.database}")
+    private int database;
+    // 定义一个Bean,名称为getRedisson,返回一个RedissonClient实例,Spring会在需要RedissonClient的地方注入这个实例
+    @Bean
+    public RedissonClient getRedisson(){
+
+        // 创建一个Redisson的配置对象z
+        Config config = new Config();
+
+        // 配置Redisson使用单机模式,并设置Redis服务器的地址
+        config.useSingleServer().setAddress("redis://"+host+":"+port)
+
+                // 设置要使用的Redis数据库索引
+                .setDatabase(database)
+                ;
+
+
+        // 设置锁的看门狗超时时间为2000毫秒。看门狗用于自动延长锁的持有时间,防止业务执行时间过长导致锁提前释放
+        config.setLockWatchdogTimeout(2000);
+
+        // 根据配置创建并返回RedissonClient实例
+        return Redisson.create(config);
+    }
+}

+ 19 - 0
src/main/java/com/zhentao/config/interceptorconfig.java

@@ -0,0 +1,19 @@
+package com.zhentao.config;
+
+import com.zhentao.intercepoter.Userinterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
+
+@Configuration
+public class interceptorconfig extends WebMvcConfigurationSupport {
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/**");
+    }
+    @Bean
+    public Userinterceptor authenticationInterceptor() {
+        return new Userinterceptor();
+    }
+}

+ 29 - 0
src/main/java/com/zhentao/enums/ApiServerException.java

@@ -0,0 +1,29 @@
+package com.zhentao.enums;
+
+public enum ApiServerException implements BaseExceptionEnum{
+    SUCCESS(1, "成功"),
+    NULL_USERNAME(1,"用户名错误"),
+    SUCCESS_login(1,"登录成功"),
+    TOKEN_ERR(101,"token错误,请重新登录"),
+    INTERRUPT(1,"操作中断"),
+    REGISTERED(1,"已注册"),
+    NOTE_ERROR(1,"验证码错误"),
+    EMAIL_EXIST(1,"邮箱已存在"),
+    NULL_PASSWORD(1,"密码错误");
+
+    ApiServerException(Integer code,String msg){
+        this.code=code;
+        this.msg=msg;
+    }
+    @Override
+    public Integer getCode() {
+        return code;
+    }
+
+    @Override
+    public String getMsg() {
+        return msg;
+    }
+    private Integer code;
+    private String msg;
+}

+ 6 - 0
src/main/java/com/zhentao/enums/BaseExceptionEnum.java

@@ -0,0 +1,6 @@
+package com.zhentao.enums;
+
+public interface BaseExceptionEnum {
+    Integer getCode();
+    String getMsg();
+}

+ 15 - 0
src/main/java/com/zhentao/exception/AsynException.java

@@ -0,0 +1,15 @@
+package com.zhentao.exception;
+
+import com.zhentao.enums.BaseExceptionEnum;
+import lombok.Data;
+
+@Data
+public class AsynException extends RuntimeException{
+    private Integer code;
+    private String msg;
+    public AsynException(BaseExceptionEnum baseExceptionEnum){
+        super(baseExceptionEnum.getMsg());
+        this.code = baseExceptionEnum.getCode();
+        this.msg = baseExceptionEnum.getMsg();
+    }
+}

+ 55 - 0
src/main/java/com/zhentao/exception/GlobalExceptionHandler.java

@@ -0,0 +1,55 @@
+package com.zhentao.exception;
+
+
+import com.zhentao.vo.Result;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.multipart.MultipartException;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Slf4j
+@RestControllerAdvice
+@ControllerAdvice
+public class GlobalExceptionHandler {
+    // 捕获所有异常的处理器,指定捕获的异常类型为Exception
+    @ExceptionHandler(value = Exception.class)
+// 将处理结果以JSON格式返回
+    @ResponseBody
+    public Result defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
+        // 记录异常信息到日志,这里只记录异常信息,不记录堆栈信息
+        log.error(e.getMessage());
+
+        // 判断异常类型并进行处理
+        if (e instanceof org.springframework.web.servlet.NoHandlerFoundException) {
+            // 如果是404异常,即没有找到处理器
+            return Result.error(404,"不存在页面请求");
+        }
+
+        if (e instanceof AsynException) {
+            // 如果是自定义的AsynException
+            AsynException customException = (AsynException) e;
+
+            // 返回自定义异常的错误码和错误信息
+            return Result.error(customException.getCode(), customException.getMessage());
+        } else if (e instanceof MultipartException) {
+            // 如果是文件上传异常
+            log.error("系统异常{}", e); // 记录异常堆栈信息
+            return Result.error(1000, "上传文件异常");
+        } else if (e instanceof MethodArgumentNotValidException) {
+            // 如果是校验异常,例如使用@Valid注解校验参数失败
+            MethodArgumentNotValidException methodArgumentNotValidException = (MethodArgumentNotValidException) e;
+
+            // 获取校验失败的第一个字段的错误信息
+            return Result.error(1002, methodArgumentNotValidException.getBindingResult().getFieldError().getDefaultMessage());
+        } else {
+            // 其他未处理的异常
+            log.error("系统异常{}", e); // 记录异常堆栈信息
+            return Result.error(1001, "系统参数异常");
+        }
+    }
+}

+ 49 - 0
src/main/java/com/zhentao/groups/MongoDB/controller/GroupMessageController.java

@@ -0,0 +1,49 @@
+package com.zhentao.groups.MongoDB.controller;
+
+import com.zhentao.config.NullLogin;
+import com.zhentao.groups.MongoDB.pojo.GroupMessage;
+import com.zhentao.groups.MongoDB.pojo.Message;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Date;
+
+@RequestMapping("/groupMessage")
+@RestController
+public class GroupMessageController {
+
+    @Autowired
+    RedisTemplate<String,String> redisTemplate;
+
+    @Autowired
+    private MongoTemplate mongoTemplate;
+
+
+    @PostMapping("/cunchugroupMessage")
+    @NullLogin
+    public String groupMessage(@RequestBody Message message)
+    {
+        GroupMessage groupMessage = new GroupMessage();
+        groupMessage.setGroupId(message.getGroupId());
+        groupMessage.setSenderId(message.getFromUserId());
+        groupMessage.setContent(message.getContent());
+        groupMessage.setType(message.getType());
+        groupMessage.setTimestamp(System.currentTimeMillis());
+        mongoTemplate.save(groupMessage);
+        return "success";
+    }
+
+
+
+
+
+
+
+
+}

+ 47 - 0
src/main/java/com/zhentao/groups/MongoDB/pojo/GroupMessage.java

@@ -0,0 +1,47 @@
+package com.zhentao.groups.MongoDB.pojo;
+
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.index.Indexed;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.Date;
+
+@Slf4j
+@Document(collection = "group_messages")
+@Data
+public class GroupMessage {
+    @Id
+    private String id;
+
+    /**
+     * 群ID
+     */
+    private Long groupId;
+
+    /**
+     * 发送者ID
+     */
+    private String senderId;
+
+    /**
+     * 消息内容
+     */
+    private String content;
+
+    /**
+     * 消息类型
+     */
+    private String type;
+
+    /**
+     * 消息时间戳
+     */
+    private Long timestamp;
+
+    /**
+     * 创建时间
+     */
+    private Date createdAt;
+}

+ 47 - 0
src/main/java/com/zhentao/groups/MongoDB/pojo/Message.java

@@ -0,0 +1,47 @@
+package com.zhentao.groups.MongoDB.pojo;
+
+import lombok.Data;
+import java.util.Date;
+
+@Data
+public class Message {
+    /**
+     * 消息类型
+     */
+    private String type;
+
+    /**
+     * 发送者ID
+     */
+    private String fromUserId;
+
+    /**
+     * 接收者ID
+     */
+    private String toUserId;
+
+    /**
+     * 群ID(群聊消息时使用)
+     */
+    private Long groupId;
+
+    /**
+     * 消息内容
+     */
+    private String content;
+
+    /**
+     * 消息时间戳
+     */
+    private Long timestamp;
+
+    /**
+     * 消息创建时间
+     */
+    private String fileUrl;      // 文件访问URL
+    private String fileName;     // 原始文件名
+    private String fileType;     // 文件MIME类型
+    private Long fileSize;       // 文件大小(字节)
+    private String avatar;
+
+}

+ 88 - 0
src/main/java/com/zhentao/groups/controller/groupsController.java

@@ -0,0 +1,88 @@
+package com.zhentao.groups.controller;
+
+import com.zhentao.groups.dto.*;
+import com.zhentao.groups.service.GroupsService;
+import com.zhentao.tool.TokenUtils;
+import com.zhentao.vo.Result;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@RequestMapping("/group")
+@RestController
+public class groupsController {
+
+
+    @Autowired
+    private GroupsService groupsService;
+
+
+//  创建群聊
+    @PostMapping("/addGroup")
+    public Result addGroup(@RequestBody AddGroupsDto addGroupsDto, @RequestHeader("token") String token){
+        Long userIdFromToken = Long.valueOf(TokenUtils.getUserIdFromToken(token));
+        addGroupsDto.setCreatorId(userIdFromToken);
+        return groupsService.addGroup(addGroupsDto);
+    }
+
+
+//    添加好友
+    @PostMapping("/AddGroupMembers")
+    public Result AddGroupMembers(@RequestBody AddGroupMembers addGroupMembers){
+        return groupsService.AddGroupMembers(addGroupMembers);
+    }
+
+//    展示可以拉取的好友
+    @PostMapping("/selectAddGroup")
+    public Result selectAddGroup(@RequestBody AddUserLoginDto addUserLoginDto,@RequestHeader("token") String token){
+        Long userIdFromToken = Long.valueOf(TokenUtils.getUserIdFromToken(token));
+        addUserLoginDto.setUid(userIdFromToken);
+        return groupsService.selectAddGroup(addUserLoginDto);
+    }
+
+//    提出群聊
+    @PostMapping("/DelGroupMembers")
+    public Result DelGroupMembers(@RequestBody DelGroupMembers delGroupMembers,@RequestHeader("token") String token){
+        Long userIdFromToken = Long.valueOf(TokenUtils.getUserIdFromToken(token));
+        delGroupMembers.setUId(userIdFromToken);
+        return groupsService.DelGroupMembers(delGroupMembers);
+    }
+
+//  查询群聊聊的用户
+    @PostMapping("/getGroupUsers")
+    public Result getGroupUsers(@RequestBody AddUserLoginDto addUserLoginDto){
+        return groupsService.getGroupUsers(addUserLoginDto);
+    }
+
+    // 根据群ID获取群详细信息
+    @GetMapping("/getGroupDetails/{groupId}")
+    public Result getGroupDetails(@PathVariable Long groupId, @RequestHeader("token") String token){
+        // 可以选择在这里添加权限验证,确保只有群成员或管理员才能查看
+        return groupsService.getGroupDetails(groupId);
+    }
+
+//    更改用户角色
+    @PostMapping("/updateGroupMemberRole")
+    public Result updateGroupMemberRole(@RequestBody UpdateGroupMemberRoleDto updateGroupMemberRoleDto){
+        return groupsService.updateGroupMemberRole(updateGroupMemberRoleDto);
+    }
+
+
+    // 退出群聊
+    @PostMapping("/outGroup")
+    public Result outGroup(@RequestBody OutGroupDto outGroupDto, @RequestHeader("token") String token){
+        Long currentUserId = Long.valueOf(TokenUtils.getUserIdFromToken(token));
+        return groupsService.outGroup(outGroupDto, currentUserId);
+    }
+
+    //    解散群聊
+    @PostMapping("/delGroup")
+    public Result delGroup(@RequestBody OutGroupsDto outGroupsDto, @RequestHeader("token") String token){
+        Long userIdFromToken = Long.valueOf(TokenUtils.getUserIdFromToken(token));
+        outGroupsDto.setUid(userIdFromToken);
+        return groupsService.delGroup(outGroupsDto);
+    }
+
+
+
+
+}

+ 23 - 0
src/main/java/com/zhentao/groups/dto/AddGroupMembers.java

@@ -0,0 +1,23 @@
+package com.zhentao.groups.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class AddGroupMembers  implements Serializable {
+
+//  群id
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long groupId;
+
+    private List<Long> userId;
+
+    private String groupssId;
+}

+ 39 - 0
src/main/java/com/zhentao/groups/dto/AddGroupsDto.java

@@ -0,0 +1,39 @@
+package com.zhentao.groups.dto;
+
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class AddGroupsDto {
+    /**
+     * 群名称
+     */
+    private String name;
+
+    /**
+     * 创建者ID
+     */
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long creatorId;
+
+    /**
+     * 群头像
+     */
+    private String avatar;
+
+    /**
+     * 群公告
+     */
+    private String announcement;
+
+    /**
+     * 群描述
+     */
+    private String description;
+
+}

+ 14 - 0
src/main/java/com/zhentao/groups/dto/AddUserLoginDto.java

@@ -0,0 +1,14 @@
+package com.zhentao.groups.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+@Data
+public class AddUserLoginDto {
+//    群id
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long id;
+//    登录的用户
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long uid;
+}

+ 23 - 0
src/main/java/com/zhentao/groups/dto/DelGroupMembers.java

@@ -0,0 +1,23 @@
+package com.zhentao.groups.dto;
+
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class DelGroupMembers {
+//  组id
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    Long groupssId;
+//  踢出的用户
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    Long userId;
+//  当前登录的用户id
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    Long  uId;
+
+}

+ 11 - 0
src/main/java/com/zhentao/groups/dto/GroupDto.java

@@ -0,0 +1,11 @@
+package com.zhentao.groups.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class GroupDto {
+    private Long groupId;
+    private List<Long> uid;
+}

+ 10 - 0
src/main/java/com/zhentao/groups/dto/OutGroupDto.java

@@ -0,0 +1,10 @@
+package com.zhentao.groups.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+@Data
+public class OutGroupDto {
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long groupsId;
+} 

+ 19 - 0
src/main/java/com/zhentao/groups/dto/OutGroupsDto.java

@@ -0,0 +1,19 @@
+package com.zhentao.groups.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class OutGroupsDto {
+
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long groupsId;
+
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long uid;
+
+}

+ 13 - 0
src/main/java/com/zhentao/groups/dto/UpdateGroupMemberRoleDto.java

@@ -0,0 +1,13 @@
+package com.zhentao.groups.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+@Data
+public class UpdateGroupMemberRoleDto {
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long groupssId;
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long userId;
+    private Integer role; // 0-普通成员, 1-管理员
+} 

+ 20 - 0
src/main/java/com/zhentao/groups/mapper/GroupMembersMapper.java

@@ -0,0 +1,20 @@
+package com.zhentao.groups.mapper;
+
+import com.zhentao.groups.pojo.GroupMembers;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+* @author lzy
+* @description 针对表【group_members(群组成员表)】的数据库操作Mapper
+* @createDate 2025-06-04 16:00:48
+* @Entity com.zhentao.groups.pojo.GroupMembers
+*/
+@Mapper
+public interface GroupMembersMapper extends BaseMapper<GroupMembers> {
+
+}
+
+
+
+

+ 20 - 0
src/main/java/com/zhentao/groups/mapper/GroupsMapper.java

@@ -0,0 +1,20 @@
+package com.zhentao.groups.mapper;
+
+import com.zhentao.groups.pojo.Groupss;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+* @author lzy
+* @description 针对表【groups(群组表)】的数据库操作Mapper
+* @createDate 2025-06-04 16:00:48
+* @Entity com.zhentao.groups.pojo.Groups
+*/
+@Mapper
+public interface GroupsMapper extends BaseMapper<Groupss> {
+
+}
+
+
+
+

+ 116 - 0
src/main/java/com/zhentao/groups/pojo/GroupMembers.java

@@ -0,0 +1,116 @@
+package com.zhentao.groups.pojo;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+/**
+ * 群组成员表
+ * @TableName group_members
+ */
+@TableName(value ="group_members")
+@Data
+public class GroupMembers implements Serializable {
+    /**
+     *
+     */
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    @TableId
+    private Long id;
+
+    /**
+     * 群ID
+     */
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long groupId;
+
+    /**
+     * 用户ID
+     */
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long userId;
+
+    /**
+     * 群昵称
+     */
+    private String nickname;
+
+    /**
+     * 角色(0-成员,1-管理员,2-群主)
+     */
+    private Integer role;
+
+    /**
+     * 加入时间
+     */
+    private Date joinTime;
+
+    /**
+     * 最后确认的消息ID
+     */
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long lastAckMsgId;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        GroupMembers other = (GroupMembers) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getGroupId() == null ? other.getGroupId() == null : this.getGroupId().equals(other.getGroupId()))
+            && (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId()))
+            && (this.getNickname() == null ? other.getNickname() == null : this.getNickname().equals(other.getNickname()))
+            && (this.getRole() == null ? other.getRole() == null : this.getRole().equals(other.getRole()))
+            && (this.getJoinTime() == null ? other.getJoinTime() == null : this.getJoinTime().equals(other.getJoinTime()))
+            && (this.getLastAckMsgId() == null ? other.getLastAckMsgId() == null : this.getLastAckMsgId().equals(other.getLastAckMsgId()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getGroupId() == null) ? 0 : getGroupId().hashCode());
+        result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
+        result = prime * result + ((getNickname() == null) ? 0 : getNickname().hashCode());
+        result = prime * result + ((getRole() == null) ? 0 : getRole().hashCode());
+        result = prime * result + ((getJoinTime() == null) ? 0 : getJoinTime().hashCode());
+        result = prime * result + ((getLastAckMsgId() == null) ? 0 : getLastAckMsgId().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", groupId=").append(groupId);
+        sb.append(", userId=").append(userId);
+        sb.append(", nickname=").append(nickname);
+        sb.append(", role=").append(role);
+        sb.append(", joinTime=").append(joinTime);
+        sb.append(", lastAckMsgId=").append(lastAckMsgId);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 139 - 0
src/main/java/com/zhentao/groups/pojo/Groupss.java

@@ -0,0 +1,139 @@
+package com.zhentao.groups.pojo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+/**
+ * 群组表
+ * @TableName groups
+ */
+@TableName(value ="groupss")
+@Data
+public class Groupss implements Serializable {
+    /**
+     * id
+
+     */
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    @TableId
+    private Long groupId;
+
+    /**
+     * 群名称
+     */
+    private String name;
+
+    /**
+     * 创建者ID
+     */
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long creatorId;
+
+    /**
+     * 群头像
+     */
+    private String avatar;
+
+    /**
+     * 群公告
+     */
+    private String announcement;
+
+    /**
+     * 群描述
+     */
+    private String description;
+
+    /**
+     * 最大成员数
+     */
+    private Integer maxMembers;
+
+    /**
+     * 状态(0-解散,1-正常)
+     */
+    private Integer status;
+
+    /**
+     * 创建时间
+     */
+    private Date createdAt;
+
+    /**
+     * 修改时间
+     */
+    private Date updatedAt;
+    @TableField(exist = false)
+    private List<GroupMembers> list;
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        Groupss other = (Groupss) that;
+        return (this.getGroupId() == null ? other.getGroupId() == null : this.getGroupId().equals(other.getGroupId()))
+            && (this.getName() == null ? other.getName() == null : this.getName().equals(other.getName()))
+            && (this.getCreatorId() == null ? other.getCreatorId() == null : this.getCreatorId().equals(other.getCreatorId()))
+            && (this.getAvatar() == null ? other.getAvatar() == null : this.getAvatar().equals(other.getAvatar()))
+            && (this.getAnnouncement() == null ? other.getAnnouncement() == null : this.getAnnouncement().equals(other.getAnnouncement()))
+            && (this.getDescription() == null ? other.getDescription() == null : this.getDescription().equals(other.getDescription()))
+            && (this.getMaxMembers() == null ? other.getMaxMembers() == null : this.getMaxMembers().equals(other.getMaxMembers()))
+            && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus()))
+            && (this.getCreatedAt() == null ? other.getCreatedAt() == null : this.getCreatedAt().equals(other.getCreatedAt()))
+            && (this.getUpdatedAt() == null ? other.getUpdatedAt() == null : this.getUpdatedAt().equals(other.getUpdatedAt()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getGroupId() == null) ? 0 : getGroupId().hashCode());
+        result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
+        result = prime * result + ((getCreatorId() == null) ? 0 : getCreatorId().hashCode());
+        result = prime * result + ((getAvatar() == null) ? 0 : getAvatar().hashCode());
+        result = prime * result + ((getAnnouncement() == null) ? 0 : getAnnouncement().hashCode());
+        result = prime * result + ((getDescription() == null) ? 0 : getDescription().hashCode());
+        result = prime * result + ((getMaxMembers() == null) ? 0 : getMaxMembers().hashCode());
+        result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode());
+        result = prime * result + ((getCreatedAt() == null) ? 0 : getCreatedAt().hashCode());
+        result = prime * result + ((getUpdatedAt() == null) ? 0 : getUpdatedAt().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", groupId=").append(groupId);
+        sb.append(", name=").append(name);
+        sb.append(", creatorId=").append(creatorId);
+        sb.append(", avatar=").append(avatar);
+        sb.append(", announcement=").append(announcement);
+        sb.append(", description=").append(description);
+        sb.append(", maxMembers=").append(maxMembers);
+        sb.append(", status=").append(status);
+        sb.append(", createdAt=").append(createdAt);
+        sb.append(", updatedAt=").append(updatedAt);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 13 - 0
src/main/java/com/zhentao/groups/service/GroupMembersService.java

@@ -0,0 +1,13 @@
+package com.zhentao.groups.service;
+
+import com.zhentao.groups.pojo.GroupMembers;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author lzy
+* @description 针对表【group_members(群组成员表)】的数据库操作Service
+* @createDate 2025-06-04 16:00:48
+*/
+public interface GroupMembersService extends IService<GroupMembers> {
+
+}

+ 54 - 0
src/main/java/com/zhentao/groups/service/GroupsService.java

@@ -0,0 +1,54 @@
+package com.zhentao.groups.service;
+
+
+import com.zhentao.groups.dto.*;
+import com.zhentao.groups.pojo.Groupss;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhentao.user.domain.UserLogin;
+import com.zhentao.vo.Result;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+
+import java.util.List;
+
+/**
+* @author lzy
+* @description 针对表【groups(群组表)】的数据库操作Service
+* @createDate 2025-06-04 16:00:48
+*/
+public interface GroupsService extends IService<Groupss> {
+
+//    创建群聊
+    Result addGroup(com.zhentao.groups.dto.AddGroupsDto addGroupsDto);
+
+
+//        添加人数
+    Result AddGroupMembers(AddGroupMembers addGroupMembers);
+//    拉人的列表
+    Result selectAddGroup(AddUserLoginDto addUserLoginDto);
+
+
+
+//    删除人数
+    Result DelGroupMembers(DelGroupMembers delGroupMembers);
+//  查询所有的一个群ID
+    List<GroupDto> getList();
+
+
+//    查询该群聊的用户信息
+    public Result getGroupUsers(AddUserLoginDto groupId);
+
+//    根据群ID查询群详细信息
+    Result getGroupDetails(Long groupId);
+
+    // 更新群成员角色
+    Result updateGroupMemberRole(UpdateGroupMemberRoleDto updateGroupMemberRoleDto);
+
+
+
+    //     退出群聊
+    Result outGroup(OutGroupDto outGroupDto, Long currentUserId);
+    //    解散群聊
+    Result delGroup(OutGroupsDto outGroupsDto);
+
+}

+ 22 - 0
src/main/java/com/zhentao/groups/service/impl/GroupMembersServiceImpl.java

@@ -0,0 +1,22 @@
+package com.zhentao.groups.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.groups.pojo.GroupMembers;
+import com.zhentao.groups.service.GroupMembersService;
+import com.zhentao.groups.mapper.GroupMembersMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author lzy
+* @description 针对表【group_members(群组成员表)】的数据库操作Service实现
+* @createDate 2025-06-04 16:00:48
+*/
+@Service
+public class GroupMembersServiceImpl extends ServiceImpl<GroupMembersMapper, GroupMembers>
+    implements GroupMembersService{
+
+}
+
+
+
+

+ 444 - 0
src/main/java/com/zhentao/groups/service/impl/GroupsServiceImpl.java

@@ -0,0 +1,444 @@
+package com.zhentao.groups.service.impl;
+
+import cn.hutool.core.util.IdUtil;
+import com.aliyuncs.endpoint.UserCustomizedEndpointResolver;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.groups.dto.*;
+import com.zhentao.groups.mapper.GroupMembersMapper;
+import com.zhentao.groups.pojo.GroupMembers;
+import com.zhentao.groups.pojo.Groupss;
+import com.zhentao.groups.service.GroupMembersService;
+import com.zhentao.groups.service.GroupsService;
+import com.zhentao.groups.mapper.GroupsMapper;
+import com.zhentao.groups.vo.GroupsVo;
+import com.zhentao.groups.vo.UserVo;
+import com.zhentao.information.cache.GroupMemberCache;
+import com.zhentao.shouye.domain.UserShouye;
+import com.zhentao.shouye.mapper.UserShouyeMapper;
+import com.zhentao.shouye.service.UserShouyeService;
+import com.zhentao.user.domain.UserLogin;
+import com.zhentao.user.dto.UserRegister;
+import com.zhentao.user.mapper.UserLoginMapper;
+import com.zhentao.userRelationships.domain.UserRelationships;
+import com.zhentao.userRelationships.mapper.UserRelationshipsMapper;
+import com.zhentao.vo.Result;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static io.lettuce.core.BitFieldArgs.Builder.set;
+
+/**
+* @author lzy
+* @description 针对表【groups(群组表)】的数据库操作Service实现
+* @createDate 2025-06-04 16:00:48
+*/
+@Service
+public class GroupsServiceImpl extends ServiceImpl<GroupsMapper, Groupss>
+    implements GroupsService{
+
+    @Autowired
+    private GroupMembersMapper groupMembersMapper;
+    @Autowired
+    private RedisTemplate<String,String> redisTemplate;
+
+    @Autowired
+    private UserShouyeMapper userShouyeMapper;
+//  创建群
+    @Autowired
+    private GroupMembersService groupMembersService;
+    @Autowired
+    private UserShouyeService userShouyeService;
+    @Autowired
+    private GroupsMapper groupsMapper;
+    @Autowired
+    private UserLoginMapper userLoginMapper;
+
+    @Autowired
+    private GroupMemberCache groupMemberCache;
+
+    @Autowired
+    private UserRelationshipsMapper userRelationshipsMapper;
+//  穿件群聊
+    @Override
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, timeout = 30)
+    public Result addGroup(AddGroupsDto addGroupsDto) {
+        Groupss groups = new Groupss();
+        groups.setGroupId(IdUtil.getSnowflake().nextId());
+        groups.setName(addGroupsDto.getName());
+        groups.setCreatorId(addGroupsDto.getCreatorId());
+        groups.setAvatar(addGroupsDto.getAvatar());
+        groups.setAnnouncement(addGroupsDto.getAnnouncement());
+        groups.setDescription(addGroupsDto.getDescription());
+        groups.setMaxMembers(200);
+        groups.setStatus(1);
+        groups.setCreatedAt(new Date());
+        groups.setUpdatedAt(new Date());
+        int insert1 = this.baseMapper.insert(groups);
+
+        GroupMembers groupMembers = new GroupMembers();
+        groupMembers.setId(IdUtil.getSnowflake().nextId());
+        groupMembers.setGroupId(groups.getGroupId());
+        groupMembers.setUserId(groups.getCreatorId());
+        groupMembers.setNickname(groups.getName());
+        groupMembers.setRole(2);
+        groupMembers.setJoinTime(new Date());
+        int insert = groupMembersMapper.insert(groupMembers);
+
+        // 更新群成员缓存
+        List<Long> memberIds = new ArrayList<>();
+        memberIds.add(groups.getCreatorId());
+        groupMemberCache.updateGroupMembers(groups.getGroupId(), memberIds);
+
+        UserShouye userShouye=new UserShouye();
+        userShouye.setId(IdUtil.getSnowflake().nextId());
+        userShouye.setUid1(addGroupsDto.getCreatorId());
+        userShouye.setUid2(null);
+        userShouye.setGid(groups.getGroupId());
+        userShouye.setStatus(0);
+        userShouye.setSort(null);
+        int insert2 = userShouyeMapper.insert(userShouye);
+
+        if (insert1!=0 && insert!=0 && insert2!=0){
+            return Result.OK(null,"创建成功");
+        }
+        return Result.ERR(null,"创建失败");
+    }
+
+//    添加人数
+    @Override
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, timeout = 30)
+    public Result AddGroupMembers(AddGroupMembers addGroupMembers) {
+        List<GroupMembers> data = new ArrayList<>();
+        List<UserShouye> userShouyes = new ArrayList<>();
+        Groupss groupss = this.getById(addGroupMembers.getGroupssId());
+        List<GroupMembers> list = groupMembersService.list(new LambdaQueryWrapper<GroupMembers>().eq(GroupMembers::getGroupId, addGroupMembers.getGroupssId()));
+
+        if (list.size() != 0) {
+            if (groupss.getMaxMembers() > list.size()) {
+                for (Long groupMembers : addGroupMembers.getUserId()) {
+                    GroupMembers groupMembers1 = new GroupMembers();
+                    groupMembers1.setId(IdUtil.getSnowflake().nextId());
+                    groupMembers1.setGroupId(groupss.getGroupId());
+                    groupMembers1.setUserId(groupMembers);
+                    groupMembers1.setNickname(groupss.getName());
+                    groupMembers1.setRole(0);
+                    groupMembers1.setJoinTime(new Date());
+                    data.add(groupMembers1);
+
+                    UserShouye userShouye = new UserShouye();
+                    userShouye.setId(IdUtil.getSnowflake().nextId());
+                    userShouye.setUid1(groupMembers);
+                    userShouye.setUid2(null);
+                    userShouye.setGid(groupss.getGroupId());
+                    userShouye.setStatus(0);
+                    userShouye.setSort(null);
+                    userShouyes.add(userShouye);
+                }
+                boolean b1 = groupMembersService.saveBatch(data);
+                boolean b = userShouyeService.saveBatch(userShouyes);
+                if (b1 && b) {
+                    // 更新群成员缓存
+                    List<Long> memberIds = list.stream()
+                        .map(GroupMembers::getUserId)
+                        .collect(Collectors.toList());
+                    memberIds.addAll(addGroupMembers.getUserId());
+                    groupMemberCache.updateGroupMembers(groupss.getGroupId(), memberIds);
+                    return Result.OK(null, "添加成功");
+                }
+            } else {
+                return Result.ERR(null, "群已满");
+            }
+        } else {
+            return Result.ERR(null, "群已不存在");
+        }
+
+        return Result.ERR(null, "添加失败");
+    }
+//  查询拉人的用户
+    @Override
+    public Result selectAddGroup(AddUserLoginDto addUserLoginDto) {
+        List<UserRelationships> userRelationships = userRelationshipsMapper.selectList(new LambdaQueryWrapper<UserRelationships>().eq(UserRelationships::getUserId, addUserLoginDto.getUid()));
+        if (userRelationships.size()==0){
+            return Result.ERR(null,"请先添加好友");
+        }
+        List<Long> friendId = userRelationships.stream().map(UserRelationships::getFriendId).collect(Collectors.toList());
+        List<GroupMembers> list = groupMembersMapper.selectList(new LambdaQueryWrapper<GroupMembers>().eq(GroupMembers::getGroupId, addUserLoginDto.getId()).in(GroupMembers::getUserId, friendId));
+
+        List<Long> collect = list.stream().map(GroupMembers::getUserId).collect(Collectors.toList());
+        if (collect.contains(friendId)){
+            return Result.ERR(null,"你的好友都已经加入该群了");
+        }
+
+        List<Long> collect1 = Stream.concat(collect.stream(), friendId.stream())
+                .collect(Collectors.toList())
+                .stream()
+                .filter(e -> Collections.frequency(
+                        Stream.concat(collect.stream(), friendId.stream()).collect(Collectors.toList()),
+                        e
+                ) == 1)
+                .distinct()
+                .collect(Collectors.toList());
+        if (collect1.size()==0){
+            return Result.ERR(null,"你的好友都已经在群中了");
+        }
+        List<UserLogin> userLogins = userLoginMapper.selectBatchIds(collect1);
+
+        return Result.OK(userLogins,"查询成功");
+    }
+
+
+    //     踢人
+    @Override
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, timeout = 30)
+    public Result DelGroupMembers(DelGroupMembers delGroupMembers) {
+        System.err.println(delGroupMembers);
+//       用户
+        GroupMembers groupMembers = groupMembersMapper.selectOne(new LambdaQueryWrapper<GroupMembers>().eq(GroupMembers::getGroupId, delGroupMembers.getGroupssId()).eq(GroupMembers::getUserId, delGroupMembers.getUId()));
+//       被踢掉的人
+        GroupMembers groupMembers2 = groupMembersMapper.selectOne(new LambdaQueryWrapper<GroupMembers>().eq(GroupMembers::getUserId, delGroupMembers.getUserId()).eq(GroupMembers::getGroupId, delGroupMembers.getGroupssId()));
+        System.err.println(groupMembers2);
+        UserShouye userShouye = userShouyeMapper.selectOne(new LambdaQueryWrapper<UserShouye>().eq(UserShouye::getUid1, delGroupMembers.getUserId()).eq(UserShouye::getGid, delGroupMembers.getGroupssId()));
+        System.err.println(userShouye);
+        if (groupMembers2!=null && groupMembers!=null){
+            //        首页展示userShouye表中数据
+            if (groupMembers.getRole()==2){
+                if (groupMembers2.getRole()==2){
+                    return Result.ERR(null,"群主不能踢自己");
+                }
+                groupMembersMapper.deleteById(groupMembers2);
+                userShouyeMapper.deleteById(userShouye);
+
+                // 更新群成员缓存
+                List<GroupMembers> remainingMembers = groupMembersService.list(
+                    new LambdaQueryWrapper<GroupMembers>()
+                        .eq(GroupMembers::getGroupId, delGroupMembers.getGroupssId())
+                );
+                List<Long> memberIds = remainingMembers.stream()
+                    .map(GroupMembers::getUserId)
+                    .collect(Collectors.toList());
+                groupMemberCache.updateGroupMembers(delGroupMembers.getGroupssId(), memberIds);
+
+                return Result.OK(null,"删除成功");
+            }else if (groupMembers.getRole()==1){
+                if (groupMembers2.getRole()==0){
+                    groupMembersMapper.deleteById(groupMembers2);
+                    userShouyeMapper.deleteById(userShouye);
+
+                    // 更新群成员缓存
+                    List<GroupMembers> remainingMembers = groupMembersService.list(
+                        new LambdaQueryWrapper<GroupMembers>()
+                            .eq(GroupMembers::getGroupId, delGroupMembers.getGroupssId())
+                    );
+                    List<Long> memberIds = remainingMembers.stream()
+                        .map(GroupMembers::getUserId)
+                        .collect(Collectors.toList());
+                    groupMemberCache.updateGroupMembers(delGroupMembers.getGroupssId(), memberIds);
+
+                    return Result.OK(null,"删除成功");
+                }else {
+                    return Result.ERR(null,"权限不足");
+                }
+            }else {
+                return Result.ERR(null,"权限不足");
+            }
+        }else {
+            return Result.ERR(null,"该用户已经不存在");
+        }
+
+    }
+    //查询所有的群ID和群里面成员ID
+        @Override
+        public List<GroupDto> getList() {
+            List<Groupss> list = this.list();
+            List<GroupDto> list3 = new ArrayList<>();
+    //        所有的群ID
+            List<Long> collect = list.stream().map(Groupss::getGroupId).collect(Collectors.toList());
+            List<GroupMembers> list1 = groupMembersService.list();
+            for (Long c:collect) {
+                List<Long> list2 = new ArrayList<>();
+                for (GroupMembers m: list1) {
+                    if (c.equals(m.getGroupId())){
+                        list2.add(m.getUserId());
+                    }
+                }
+                GroupDto groupDto = new GroupDto();
+                groupDto.setGroupId(c);
+                groupDto.setUid(list2);
+                list3.add(groupDto);
+            }
+            return list3;
+        }
+
+//    查询群成员
+    @Override
+    public Result getGroupUsers(AddUserLoginDto groupId) {
+        Groupss groupss = this.baseMapper.selectById(groupId.getId());
+        Long groupId1 = groupss.getGroupId();
+        List<GroupMembers> list = groupMembersMapper.selectList(new LambdaQueryWrapper<GroupMembers>().eq(GroupMembers::getGroupId, groupId1));
+
+        List<Long> uid = list.stream().map(GroupMembers::getUserId).collect(Collectors.toList());
+        List<UserLogin> userLogins = userLoginMapper.selectBatchIds(uid);
+
+        GroupsVo groupsVos = new GroupsVo();
+        groupsVos.setName(groupss.getName());
+        groupsVos.setAvatar(groupss.getAvatar());
+        groupsVos.setDescription(groupss.getDescription());
+        groupsVos.setMaxMembers(groupss.getMaxMembers());
+        groupsVos.setStatus(groupss.getStatus());
+        groupsVos.setCreatedAt(groupss.getCreatedAt());
+        groupsVos.setUpdatedAt(groupss.getUpdatedAt());
+        groupsVos.setGroupId(groupss.getGroupId());
+        groupsVos.setCreatorId(groupss.getCreatorId());
+        groupsVos.setAnnouncement(groupss.getAnnouncement());
+        List<UserVo> userVos = new ArrayList<>();
+
+        Map<Long,GroupMembers> map = list.stream().collect(Collectors.toMap(GroupMembers::getUserId, groupMembers -> groupMembers));
+
+        for (UserLogin userLogin:userLogins){
+            UserVo userVo = new UserVo();
+            userVo.setUserId(userLogin.getId());
+            userVo.setNickName(userLogin.getNickName());
+            userVo.setImg(userLogin.getAvatar());
+            GroupMembers groupMembers1 = map.get(userLogin.getId());
+            userVo.setRole(groupMembers1.getRole());
+            userVos.add(userVo);
+        }
+        groupsVos.setUser(userVos);
+
+
+        return Result.OK(groupsVos,"查询成功");
+    }
+//  获取群的信息
+    @Override
+    public Result getGroupDetails(Long groupId) {
+        Groupss groupss = this.baseMapper.selectById(groupId);
+        if (groupss != null) {
+            return Result.OK(groupss, "查询成功");
+        } else {
+            return Result.ERR(null, "群聊不存在");
+        }
+    }
+
+//    设置角色
+    @Override
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, timeout = 30)
+    public Result updateGroupMemberRole(UpdateGroupMemberRoleDto updateGroupMemberRoleDto) {
+        if (updateGroupMemberRoleDto.getGroupssId() == null || updateGroupMemberRoleDto.getUserId() == null || updateGroupMemberRoleDto.getRole() == null) {
+            return Result.ERR(null, "参数不能为空");
+        }
+
+        // 查找群成员
+        LambdaQueryWrapper<GroupMembers> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(GroupMembers::getGroupId, updateGroupMemberRoleDto.getGroupssId())
+                    .eq(GroupMembers::getUserId, updateGroupMemberRoleDto.getUserId());
+        GroupMembers groupMember = groupMembersMapper.selectOne(queryWrapper);
+
+        if (groupMember == null) {
+            return Result.ERR(null, "群成员不存在");
+        }
+
+        // 检查新的角色值是否有效
+        if (updateGroupMemberRoleDto.getRole() < 0 || updateGroupMemberRoleDto.getRole() > 1) { // 0-普通成员, 1-管理员
+            return Result.ERR(null, "无效的角色值");
+        }
+
+        // 更新角色
+        groupMember.setRole(updateGroupMemberRoleDto.getRole());
+        int rowsAffected = groupMembersMapper.updateById(groupMember);
+
+        if (rowsAffected > 0) {
+            // 更新群成员缓存 (如果需要的话,这里可以重新加载整个群的成员或者单独更新)
+            List<GroupMembers> remainingMembers = groupMembersService.list(
+                new LambdaQueryWrapper<GroupMembers>()
+                    .eq(GroupMembers::getGroupId, updateGroupMemberRoleDto.getGroupssId())
+            );
+            List<Long> memberIds = remainingMembers.stream()
+                .map(GroupMembers::getUserId)
+                .collect(Collectors.toList());
+            groupMemberCache.updateGroupMembers(updateGroupMemberRoleDto.getGroupssId(), memberIds);
+            return Result.OK(null, "成员角色更新成功");
+        } else {
+            return Result.ERR(null, "成员角色更新失败");
+        }
+    }
+
+
+
+    @Override
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, timeout = 30)
+    public Result outGroup(OutGroupDto outGroupDto, Long currentUserId) {
+        if (outGroupDto.getGroupsId() == null || currentUserId == null) {
+            return Result.ERR(null, "参数不能为空");
+        }
+
+        // 查找群成员记录
+        LambdaQueryWrapper<GroupMembers> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(GroupMembers::getGroupId, outGroupDto.getGroupsId())
+                .eq(GroupMembers::getUserId, currentUserId);
+        GroupMembers memberToLeave = groupMembersMapper.selectOne(queryWrapper);
+
+        if (memberToLeave == null) {
+            return Result.ERR(null, "您不是该群的成员");
+        }
+
+        // 检查是否为群主
+        if (memberToLeave.getRole() == 2) {
+            // 如果是群主,提示需要解散群聊
+            return Result.ERR(null, "群主不能直接退出群聊,请使用解散群聊功能");
+        }
+
+        // 删除群成员记录
+        int rowsAffected = groupMembersMapper.deleteById(memberToLeave);
+        UserShouye userShouye = userShouyeMapper.selectOne(new LambdaQueryWrapper<UserShouye>().eq(UserShouye::getUid1, currentUserId).eq(UserShouye::getGid, outGroupDto.getGroupsId()));
+        if(userShouye != null) {
+            userShouyeMapper.deleteById(userShouye);
+        }
+
+        if (rowsAffected > 0) {
+            // 更新群成员缓存
+            List<GroupMembers> remainingMembers = groupMembersService.list(
+                    new LambdaQueryWrapper<GroupMembers>()
+                            .eq(GroupMembers::getGroupId, outGroupDto.getGroupsId())
+            );
+            List<Long> memberIds = remainingMembers.stream()
+                    .map(GroupMembers::getUserId)
+                    .collect(Collectors.toList());
+            groupMemberCache.updateGroupMembers(outGroupDto.getGroupsId(), memberIds);
+            return Result.OK(null, "成功退出群聊");
+        } else {
+            return Result.ERR(null, "退出群聊失败");
+        }
+    }
+
+    //    解散群聊
+    @Override
+    public Result delGroup(OutGroupsDto outGroupsDto) {
+        GroupMembers groupMembers = groupMembersMapper.selectOne(new LambdaQueryWrapper<GroupMembers>().eq(GroupMembers::getGroupId, outGroupsDto.getGroupsId()).eq(GroupMembers::getUserId, outGroupsDto.getUid()));
+        if (groupMembers.getRole()==2){
+            Groupss groupss = this.baseMapper.selectById(outGroupsDto.getGroupsId());
+            this.baseMapper.deleteById(groupss);
+            List<GroupMembers> list = groupMembersMapper.selectList(new LambdaQueryWrapper<GroupMembers>().eq(GroupMembers::getGroupId, outGroupsDto.getGroupsId()));
+            groupMembersMapper.deleteBatchIds(list);
+            List<UserShouye> userShouyes = userShouyeMapper.selectList(new LambdaQueryWrapper<UserShouye>().eq(UserShouye::getGid, outGroupsDto.getGroupsId()));
+            userShouyeMapper.deleteBatchIds(userShouyes);
+            return Result.OK(null,"解散成功");
+        }else{
+            return Result.ERR(null,"权限不足");
+        }
+    }
+
+}
+
+
+
+
+

+ 69 - 0
src/main/java/com/zhentao/groups/vo/GroupsVo.java

@@ -0,0 +1,69 @@
+package com.zhentao.groups.vo;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+@Data
+public class GroupsVo<T> {
+
+
+
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    @TableId
+    private Long groupId;
+
+    /**
+     * 群名称
+     */
+    private String name;
+
+    /**
+     * 创建者ID
+     */
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long creatorId;
+
+    /**
+     * 群头像
+     */
+    private String avatar;
+
+    /**
+     * 群公告
+     */
+    private String announcement;
+
+    /**
+     * 群描述
+     */
+    private String description;
+
+    /**
+     * 最大成员数
+     */
+    private Integer maxMembers;
+
+    /**
+     * 状态(0-解散,1-正常)
+     */
+    private Integer status;
+
+    /**
+     * 创建时间
+     */
+    private Date createdAt;
+
+    /**
+     * 修改时间
+     */
+    private Date updatedAt;
+
+    private List<UserVo> user;
+
+
+
+}

+ 19 - 0
src/main/java/com/zhentao/groups/vo/UserVo.java

@@ -0,0 +1,19 @@
+package com.zhentao.groups.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import javax.persistence.criteria.CriteriaBuilder;
+
+@Data
+public class UserVo {
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long userId;
+    private String nickName;
+
+    private String img;
+
+
+    private Integer role;
+
+}

+ 40 - 0
src/main/java/com/zhentao/handler/PointTypeHandler.java

@@ -0,0 +1,40 @@
+package com.zhentao.handler;
+
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.TypeHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.*;
+
+public class PointTypeHandler implements TypeHandler<String> {
+    private static final Logger log = LoggerFactory.getLogger(PointTypeHandler.class);
+    // 插入时将WKT字符串转换为POINT
+    @Override
+    public void setParameter(PreparedStatement ps, int i, String wkt, JdbcType jdbcType) throws SQLException {
+        if (wkt == null) {
+            ps.setNull(i, Types.OTHER);
+        } else {
+            // 关键修改:直接设置WKT字符串,不使用函数转换
+            ps.setString(i, wkt);
+            // 指定MySQL的GEOMETRY类型(4001)
+            ps.setObject(i, wkt, 4001);
+        }
+    }
+
+    // 查询时将POINT转换为WKT字符串
+    @Override
+    public String getResult(ResultSet rs, String columnName) throws SQLException {
+        return rs.getString(columnName);
+    }
+
+    @Override
+    public String getResult(ResultSet rs, int columnIndex) throws SQLException {
+        return rs.getString(columnIndex);
+    }
+
+    @Override
+    public String getResult(CallableStatement cs, int columnIndex) throws SQLException {
+        return cs.getString(columnIndex);
+    }
+}

+ 88 - 0
src/main/java/com/zhentao/information/cache/ChannelCache.java

@@ -0,0 +1,88 @@
+package com.zhentao.information.cache;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Channel缓存管理类
+ * 用于管理用户Channel
+ */
+@Slf4j
+@Component
+public class ChannelCache {
+    
+    /**
+     * 用户ID和Channel的映射关系
+     * key: 用户ID
+     * value: Channel上下文
+     */
+    private static final Map<String, ChannelHandlerContext> USER_CHANNEL_MAP = new ConcurrentHashMap<>();
+
+    private final Map<ChannelHandlerContext, String> reverseMap = new ConcurrentHashMap<>();
+
+    /**
+     * 添加用户Channel映射
+     * @param userId 用户ID
+     * @param ctx Channel上下文
+     */
+    public void addCache(String userId, ChannelHandlerContext ctx) {
+        USER_CHANNEL_MAP.put(userId, ctx);
+        reverseMap.put(ctx, userId);
+        log.info("用户 {} 的Channel已缓存", userId);
+    }
+
+    /**
+     * 获取用户的Channel
+     * @param userId 用户ID
+     * @return Channel上下文
+     */
+    public ChannelHandlerContext getCache(String userId) {
+        return USER_CHANNEL_MAP.get(userId);
+    }
+
+    /**
+     * 移除用户Channel映射
+     * @param ctx Channel上下文
+     */
+    public void removeCache(ChannelHandlerContext ctx) {
+        String userId = reverseMap.remove(ctx);
+        if (userId != null) {
+            USER_CHANNEL_MAP.remove(userId);
+            log.info("用户 {} 的Channel已移除", userId);
+        }
+    }
+
+    /**
+     * 移除用户Channel映射
+     * @param userId 用户ID
+     */
+    public void removeCache(String userId) {
+        ChannelHandlerContext ctx = USER_CHANNEL_MAP.remove(userId);
+        if (ctx != null) {
+            reverseMap.remove(ctx);
+            log.info("用户 {} 的Channel已移除", userId);
+        }
+    }
+
+    /**
+     * 获取所有用户Channel映射
+     * @return 用户Channel映射Map
+     */
+    public Map<String, ChannelHandlerContext> getAllCache() {
+        return USER_CHANNEL_MAP;
+    }
+
+    /**
+     * 判断用户是否在线
+     * @param userId 用户ID
+     * @return 是否在线
+     */
+    public boolean isOnline(String userId) {
+        return USER_CHANNEL_MAP.containsKey(userId);
+    }
+} 

+ 73 - 0
src/main/java/com/zhentao/information/cache/GroupChannelCache.java

@@ -0,0 +1,73 @@
+package com.zhentao.information.cache;
+
+import io.netty.channel.group.ChannelGroup;
+import io.netty.channel.group.DefaultChannelGroup;
+import io.netty.util.concurrent.GlobalEventExecutor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 群聊Channel缓存管理类
+ * 用于管理群聊ChannelGroup
+ */
+@Slf4j
+@Component
+public class GroupChannelCache {
+    
+    /**
+     * 群ID和ChannelGroup的映射关系
+     * key: 群ID
+     * value: ChannelGroup
+     */
+    private static final Map<Long, ChannelGroup> GROUP_CHANNEL_MAP = new ConcurrentHashMap<>();
+
+    /**
+     * 添加群聊ChannelGroup
+     * @param groupId 群ID
+     * @return ChannelGroup
+     */
+    public ChannelGroup addGroup(Long groupId) {
+        ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
+        GROUP_CHANNEL_MAP.put(groupId, channelGroup);
+        log.info("群 {} 的ChannelGroup已创建", groupId);
+        return channelGroup;
+    }
+
+    /**
+     * 获取群聊ChannelGroup
+     * @param groupId 群ID
+     * @return ChannelGroup
+     */
+    public ChannelGroup getGroup(Long groupId) {
+        return GROUP_CHANNEL_MAP.get(groupId);
+    }
+
+    /**
+     * 移除群聊ChannelGroup
+     * @param groupId 群ID
+     */
+    public void removeGroup(Long groupId) {
+        GROUP_CHANNEL_MAP.remove(groupId);
+        log.info("群 {} 的ChannelGroup已移除", groupId);
+    }
+
+    /**
+     * 获取所有群聊ChannelGroup
+     * @return 群聊ChannelGroup映射Map
+     */
+    public Map<Long, ChannelGroup> getAllGroups() {
+        return GROUP_CHANNEL_MAP;
+    }
+
+    /**
+     * 判断群聊是否存在
+     * @param groupId 群ID
+     * @return 是否存在
+     */
+    public boolean exists(Long groupId) {
+        return GROUP_CHANNEL_MAP.containsKey(groupId);
+    }
+} 

+ 97 - 0
src/main/java/com/zhentao/information/cache/GroupMemberCache.java

@@ -0,0 +1,97 @@
+package com.zhentao.information.cache;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 群成员缓存管理类
+ * 用于缓存群ID和群成员ID的映射关系
+ */
+@Slf4j
+@Component
+public class GroupMemberCache {
+    
+    /**
+     * 群ID和群成员ID列表的映射关系
+     * key: 群ID
+     * value: 群成员ID列表
+     */
+    private final Map<Long, List<Long>> groupMemberMap = new ConcurrentHashMap<>();
+
+    /**
+     * 更新群成员缓存
+     * @param groupId 群ID
+     * @param memberIds 群成员ID列表
+     */
+    public void updateGroupMembers(Long groupId, List<Long> memberIds) {
+        groupMemberMap.put(groupId, memberIds);
+        log.info("群 {} 的成员缓存已更新,成员数:{}", groupId, memberIds.size());
+    }
+
+    /**
+     * 获取群成员列表
+     * @param groupId 群ID
+     * @return 群成员ID列表
+     */
+    public List<Long> getGroupMembers(Long groupId) {
+        return groupMemberMap.get(groupId);
+    }
+
+    /**
+     * 添加群成员
+     * @param groupId 群ID
+     * @param userId 用户ID
+     */
+    public void addGroupMember(Long groupId, Long userId) {
+        List<Long> members = groupMemberMap.get(groupId);
+        if (members != null && !members.contains(userId)) {
+            members.add(userId);
+            log.info("用户 {} 已添加到群 {} 的成员缓存", userId, groupId);
+        }
+    }
+
+    /**
+     * 移除群成员
+     * @param groupId 群ID
+     * @param userId 用户ID
+     */
+    public void removeGroupMember(Long groupId, Long userId) {
+        List<Long> members = groupMemberMap.get(groupId);
+        if (members != null) {
+            members.remove(userId);
+            log.info("用户 {} 已从群 {} 的成员缓存移除", userId, groupId);
+        }
+    }
+
+    /**
+     * 判断用户是否在群中
+     * @param groupId 群ID
+     * @param userId 用户ID
+     * @return 是否在群中
+     */
+    public boolean isUserInGroup(Long groupId, Long userId) {
+        List<Long> members = groupMemberMap.get(groupId);
+        return members != null && members.contains(userId);
+    }
+
+    /**
+     * 获取所有群成员映射
+     * @return 群成员映射Map
+     */
+    public Map<Long, List<Long>> getAllGroupMembers() {
+        return groupMemberMap;
+    }
+
+    /**
+     * 清除群成员缓存
+     * @param groupId 群ID
+     */
+    public void clearGroupMembers(Long groupId) {
+        groupMemberMap.remove(groupId);
+        log.info("群 {} 的成员缓存已清除", groupId);
+    }
+} 

+ 99 - 0
src/main/java/com/zhentao/information/config/NettyConfig.java

@@ -0,0 +1,99 @@
+package com.zhentao.information.config;
+
+import com.zhentao.information.handler.WebSocketHandler;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import io.netty.handler.timeout.IdleStateHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.Resource;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Netty服务器配置类
+ * 配置WebSocket服务器的启动参数和处理器链
+ */
+@Slf4j
+@Configuration
+public class NettyConfig {
+
+    /**
+     * WebSocket服务器端口
+     */
+    @Value("${netty.websocket.port}")
+    private int port;
+
+    /**
+     * WebSocket消息处理器
+     */
+    @Resource
+    private WebSocketHandler webSocketHandler;
+
+    /**
+     * 配置并启动Netty服务器
+     * @return ServerBootstrap实例
+     */
+    @Bean
+    public ServerBootstrap serverBootstrap() {
+        // 创建主从线程组
+        // bossGroup用于接收客户端连接
+        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
+        // workerGroup用于处理客户端数据
+        EventLoopGroup workerGroup = new NioEventLoopGroup();
+        
+        // 创建服务器启动对象
+        ServerBootstrap bootstrap = new ServerBootstrap();
+        bootstrap.group(bossGroup, workerGroup)
+                // 设置服务器通道实现
+                .channel(NioServerSocketChannel.class)
+                // 设置线程队列等待连接个数
+                .option(ChannelOption.SO_BACKLOG, 128)
+                // 设置保持活动连接状态
+                .childOption(ChannelOption.SO_KEEPALIVE, true)
+                // 禁用Nagle算法,减少延迟
+                .childOption(ChannelOption.TCP_NODELAY, true)
+                // 设置处理器
+                .childHandler(new ChannelInitializer<SocketChannel>() {
+                    @Override
+                    protected void initChannel(SocketChannel ch) {
+                        // 获取管道
+                        ch.pipeline()
+                                // HTTP编解码器
+                                .addLast(new HttpServerCodec())
+                                // 支持大数据流
+                                .addLast(new ChunkedWriteHandler())
+                                // HTTP消息聚合器
+                                .addLast(new HttpObjectAggregator(65536))
+                                // 调整心跳检测时间:30秒没有收到消息就触发
+                                .addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS))
+                                // WebSocket协议处理器
+                                .addLast(new WebSocketServerProtocolHandler("/ws", null, true, 65536))
+                                // 自定义消息处理器
+                                .addLast(webSocketHandler);
+                    }
+                });
+        
+        try {
+            // 绑定端口并启动服务器
+            bootstrap.bind(port).sync();
+            log.info("Netty WebSocket服务器启动成功,端口:{}", port);
+        } catch (InterruptedException e) {
+            log.error("Netty WebSocket服务器启动失败", e);
+            Thread.currentThread().interrupt();
+        }
+        
+        return bootstrap;
+    }
+} 

+ 42 - 0
src/main/java/com/zhentao/information/config/WebSocketHandshakeInterceptor.java

@@ -0,0 +1,42 @@
+package com.zhentao.information.config;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.util.AttributeKey;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+public class WebSocketHandshakeInterceptor extends ChannelInboundHandlerAdapter {
+    public static final AttributeKey<String> PEER_ID_KEY = AttributeKey.valueOf("peerId");
+    public static final AttributeKey<String> TOKEN_KEY = AttributeKey.valueOf("token");
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        if (msg instanceof FullHttpRequest) {
+            FullHttpRequest req = (FullHttpRequest) msg;
+            URI uri = new URI(req.uri());
+            String query = uri.getQuery();
+            if (query != null) {
+                Map<String, String> params = new HashMap<>();
+                for (String param : query.split("&")) {
+                    String[] pair = param.split("=");
+                    if (pair.length == 2) {
+                        params.put(URLDecoder.decode(pair[0], String.valueOf(StandardCharsets.UTF_8)),
+                                   URLDecoder.decode(pair[1], String.valueOf(StandardCharsets.UTF_8)));
+                    }
+                }
+                if (params.containsKey("peerId")) {
+                    ctx.channel().attr(PEER_ID_KEY).set(params.get("peerId"));
+                }
+                if (params.containsKey("token")) {
+                    ctx.channel().attr(TOKEN_KEY).set(params.get("token"));
+                }
+            }
+        }
+        super.channelRead(ctx, msg);
+    }
+}

+ 37 - 0
src/main/java/com/zhentao/information/controller/FileController.java

@@ -0,0 +1,37 @@
+package com.zhentao.information.controller;
+import com.zhentao.osspicture.OssUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@RestController
+public class FileController {
+
+    @Autowired
+    private OssUtil ossUtil;
+
+    @PostMapping("/api/file/upload")
+    public Map<String, Object> uploadFile(@RequestParam("file") MultipartFile file) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            String fileUrl = ossUtil.uploadFile(file);
+            result.put("url", fileUrl);
+            result.put("fileName", file.getOriginalFilename());
+            result.put("fileType", file.getContentType());
+            result.put("fileSize", file.getSize());
+            result.put("success", true);
+            log.info("文件上传成功: {}", fileUrl);
+        } catch (Exception e) {
+            log.error("文件上传失败", e);
+            result.put("success", false);
+            result.put("message", "上传失败: " + e.getMessage());
+        }
+        return result;
+    }
+}

+ 109 - 0
src/main/java/com/zhentao/information/controller/MessageController.java

@@ -0,0 +1,109 @@
+package com.zhentao.information.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.zhentao.config.NullLogin;
+import com.zhentao.information.cache.ChannelCache;
+import com.zhentao.information.entity.ChatMessage;
+import com.zhentao.information.entity.Message;
+import com.zhentao.information.repository.ChatMessageRepository;
+import com.zhentao.tool.TokenUtils;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 消息控制器
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/message")
+public class MessageController {
+
+    @Resource
+    private ChannelCache channelCache;
+
+    @Resource
+    private ChatMessageRepository chatMessageRepository;
+
+    /**
+     * 发送消息接口
+     */
+    @PostMapping("/send")
+    @NullLogin
+    public String sendMessage(@RequestBody Message message) {
+        log.info("收到消息:发送者={}, 接收者={}, 内容={}",
+                message.getFromUserId(),
+                message.getToUserId(),
+                message.getContent());
+
+        // 生成聊天ID(确保两个用户之间的聊天ID唯一)
+        String chatId = generateChatId(message.getFromUserId(), message.getToUserId());
+
+        // 创建MongoDB消息对象
+        ChatMessage chatMessage = new ChatMessage();
+        chatMessage.setFromUserId(message.getFromUserId());
+        chatMessage.setToUserId(message.getToUserId());
+        chatMessage.setContent(message.getContent());
+        chatMessage.setType(String.valueOf(message.getType()));
+        chatMessage.setTimestamp(System.currentTimeMillis());
+        chatMessage.setIsRead(false);
+        chatMessage.setChatId(chatId);
+
+        // 保存消息到MongoDB
+        chatMessageRepository.save(chatMessage);
+
+        // 获取接收者的Channel
+        ChannelHandlerContext toUserCtx = channelCache.getCache(message.getToUserId());
+
+        if (toUserCtx != null) {
+            // 发送消息给接收者
+            toUserCtx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message)));
+            return "消息已发送";
+        } else {
+            return "消息已保存,等待接收者上线";
+        }
+    }
+
+    /**
+     * 获取两个用户之间的聊天记录
+     */
+    @GetMapping("/history")
+    @NullLogin
+    public List<ChatMessage> getChatHistory(@RequestHeader("token") String token, @RequestParam String userId2) {
+        String userIdFromToken = TokenUtils.getUserIdFromToken(token);
+        String chatId = generateChatId(userIdFromToken, userId2);
+        return chatMessageRepository.findByChatId(chatId);
+    }
+
+    /**
+     * 获取用户的未读消息
+     */
+    @GetMapping("/unread")
+    @NullLogin
+    public List<ChatMessage> getUnreadMessages(@RequestParam String userId) {
+        return chatMessageRepository.findByToUserIdAndIsReadFalse(userId);
+    }
+
+    /**
+     * 查询某个群聊的所有消息
+     */
+    @GetMapping("/group/history")
+    @NullLogin
+    public List<ChatMessage> getGroupChatHistory(@RequestParam String groupId) {
+        // 查询chatId为group_群ID的所有消息
+        return chatMessageRepository.findByChatId("group_" + groupId);
+    }
+
+    /**
+     * 生成聊天ID
+     */
+    private String generateChatId(String userId1, String userId2) {
+        return userId1.compareTo(userId2) < 0 ?
+                userId1 + "_" + userId2 :
+                userId2 + "_" + userId1;
+    }
+}

+ 48 - 0
src/main/java/com/zhentao/information/entity/ChatMessage.java

@@ -0,0 +1,48 @@
+package com.zhentao.information.entity;
+
+import lombok.Data;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+import org.springframework.data.mongodb.core.index.Indexed;
+
+/**
+ * 聊天消息实体类(MongoDB)
+ */
+@Data
+@Document(collection = "chat_messages")
+public class ChatMessage {
+
+    @Id
+    private String id;
+
+    @Indexed
+    private String fromUserId;
+
+    @Indexed
+    private String toUserId;
+
+    private String content;
+
+    private String type;
+
+    private Long timestamp;
+
+    private Boolean isRead;
+
+    // 复合索引:用于查询两个用户之间的聊天记录
+    @Indexed
+    private String chatId;
+    // 新增文件元数据
+    private String fileUrl;      // OSS访问地址
+    private String fileName;     // 原始文件名
+    private String fileType;     // MIME类型
+    private Long fileSize;       // 文件大小(字节)
+    private String avatar; // 新增:发送者头像
+
+    public String getAvatar() {
+        return avatar;
+    }
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+}

+ 29 - 0
src/main/java/com/zhentao/information/entity/Message.java

@@ -0,0 +1,29 @@
+package com.zhentao.information.entity;
+
+import lombok.Data;
+
+/**
+ * WebSocket消息实体类
+ */
+@Data
+public class Message {
+    private String type;  // 新增类型: image/video/file
+    private String fromUserId;
+    private String toUserId;
+    private Long groupId;
+    private String content;
+    private Long timestamp;
+    // 新增文件元数据字段
+    private String fileUrl;      // 文件访问URL
+    private String fileName;     // 原始文件名
+    private String fileType;     // 文件MIME类型
+    private Long fileSize;       // 文件大小(字节)
+    private String avatar; // 新增:发送者头像
+
+    public String getAvatar() {
+        return avatar;
+    }
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+}

+ 76 - 0
src/main/java/com/zhentao/information/handler/HeartbeatHandler.java

@@ -0,0 +1,76 @@
+package com.zhentao.information.handler;
+
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.timeout.IdleState;
+import io.netty.handler.timeout.IdleStateEvent;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 心跳处理器
+ * 处理客户端的心跳检测
+ */
+@Slf4j
+@Component
+@ChannelHandler.Sharable
+public class HeartbeatHandler extends ChannelInboundHandlerAdapter {
+
+    private static final int MAX_MISSED_HEARTBEATS = 3;
+    private int missedHeartbeats = 0;
+
+    /**
+     * 处理用户事件
+     * 当触发IdleStateEvent时调用
+     * @param ctx Channel上下文
+     * @param evt 事件对象
+     */
+    @Override
+    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+        if (evt instanceof IdleStateEvent) {
+            IdleStateEvent event = (IdleStateEvent) evt;
+            
+            if (event.state() == IdleState.READER_IDLE) {
+                missedHeartbeats++;
+                log.warn("读空闲,已错过 {} 次心跳", missedHeartbeats);
+                
+                if (missedHeartbeats >= MAX_MISSED_HEARTBEATS) {
+                    log.error("读空闲超时,关闭连接:{}", ctx.channel().id().asLongText());
+                    ctx.close();
+                } else {
+                    // 发送心跳检测消息
+                    ctx.writeAndFlush("ping");
+                }
+            } else if (event.state() == IdleState.WRITER_IDLE) {
+                // 发送心跳检测消息
+                ctx.writeAndFlush("ping");
+            }
+        } else {
+            super.userEventTriggered(ctx, evt);
+        }
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        if (msg instanceof String && "pong".equals(msg)) {
+            // 收到心跳响应,重置计数器
+            missedHeartbeats = 0;
+            log.debug("收到心跳响应");
+        } else {
+            super.channelRead(ctx, msg);
+        }
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+        log.info("连接断开:{}", ctx.channel().id().asLongText());
+        super.channelInactive(ctx);
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        log.error("心跳处理器异常", cause);
+        ctx.close();
+    }
+} 

+ 189 - 0
src/main/java/com/zhentao/information/handler/WebSocketHandler.java

@@ -0,0 +1,189 @@
+package com.zhentao.information.handler;
+
+import com.alibaba.fastjson.JSON;
+import com.zhentao.groups.MongoDB.pojo.GroupMessage;
+import com.zhentao.groups.MongoDB.pojo.Message;
+import com.zhentao.information.entity.ChatMessage;
+import com.zhentao.information.repository.ChatMessageRepository;
+import com.zhentao.information.service.WebSocketService;
+import com.zhentao.tool.TokenUtils;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.Date;
+
+/**
+ * WebSocket消息处理器
+ * 处理WebSocket连接、消息接收和发送
+ */
+@Slf4j
+@Component
+@ChannelHandler.Sharable
+public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
+
+    @Resource
+    private ChatMessageRepository chatMessageRepository;
+
+    @Resource
+    private WebSocketService webSocketService;
+
+    /**
+     * 处理接收到的WebSocket消息
+     */
+    @Override
+    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
+        String text = msg.text();
+        log.info("收到消息:{}", text);
+        try {
+            Message message = JSON.parseObject(text, Message.class);
+            log.info("接收到的消息:{}", message);
+
+            // 如果是连接消息,处理token
+            if ("connect".equals(message.getType())) {
+                String userId = webSocketService.handleUserLogin(message.getContent(), ctx);
+                log.info("用户 {} 登录成功", userId);
+                if (userId != null) {
+                    // 用户登录成功后,自动加入所有群聊
+                    webSocketService.joinAllGroups(userId);
+                    // 发送连接成功消息
+                    Message response = new Message();
+                    response.setType("connect_success");
+                    response.setContent("连接成功");
+                    ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(response)));
+                }
+                return;
+            }
+
+            // 如果是心跳消息
+            if ("ping".equals(message.getType())) {
+                Message pongMessage = new Message();
+                pongMessage.setType("pong");
+                ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(pongMessage)));
+                return;
+            }
+
+            // 如果是群聊消息
+            if (message.getGroupId() != null) {
+                handleGroupMessage(message);
+                return;
+            }
+
+            // 处理普通消息
+            handleMessage(message);
+
+        } catch (Exception e) {
+            log.error("处理消息失败", e);
+            // 发送错误消息给客户端
+            Message errorMessage = new Message();
+            errorMessage.setType("error");
+            errorMessage.setContent("消息处理失败");
+            ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(errorMessage)));
+        }
+    }
+
+    /**
+     * 处理群聊消息
+     */
+    private void handleGroupMessage(Message message) {
+        // 设置群聊消息类型
+        message.setType(message.getType() != null ? message.getType() : "group_chat");
+        // 广播消息给群内所有成员
+        boolean sent = webSocketService.handleGroupMessage(message);
+        if (sent) {
+            // 发送消息确认
+            Message ackMessage = new Message();
+            ackMessage.setType("message_ack");
+            ackMessage.setContent("群消息已发送");
+            webSocketService.sendMessageToUser(message.getFromUserId(), ackMessage);
+        }
+    }
+
+    /**
+     * 处理普通消息
+     */
+    private void handleMessage(Message message) {
+        // 生成聊天ID
+        String chatId = generateChatId(message.getFromUserId(), message.getToUserId());
+        ChatMessage chatMessage = new ChatMessage();
+        // 新增文件/图片/视频元数据保存
+        if (message.getFileUrl() != null) {
+            chatMessage.setFileUrl(message.getFileUrl());
+            chatMessage.setFileName(message.getFileName());
+            chatMessage.setFileType(message.getFileType());
+            chatMessage.setFileSize(message.getFileSize());
+        }
+        // 新增:保存发送者头像
+        chatMessage.setAvatar(message.getAvatar());
+        // 创建MongoDB消息对象
+        chatMessage.setFromUserId(message.getFromUserId());
+        chatMessage.setToUserId(message.getToUserId());
+        chatMessage.setContent(message.getContent());
+        chatMessage.setType(message.getType());
+        chatMessage.setTimestamp(System.currentTimeMillis());
+        chatMessage.setIsRead(false);
+        chatMessage.setChatId(chatId);
+
+        // 保存消息到MongoDB
+        chatMessageRepository.save(chatMessage);
+
+        // 发送消息给接收者
+        boolean sent = webSocketService.sendMessageToUser(message.getToUserId(), message);
+        if (sent) {
+            log.info("消息已发送给用户: {}, 内容: {}", message.getToUserId(), message.getContent());
+            // 发送消息确认给发送者
+            Message ackMessage = new Message();
+            ackMessage.setType("message_ack");
+            ackMessage.setContent("消息已发送");
+            webSocketService.sendMessageToUser(message.getFromUserId(), ackMessage);
+        } else {
+            log.info("用户 {} 不在线,消息已保存到MongoDB", message.getToUserId());
+            // 发送消息未送达通知给发送者
+            Message offlineMessage = new Message();
+            offlineMessage.setType("message_offline");
+            offlineMessage.setContent("对方不在线,消息已保存");
+            webSocketService.sendMessageToUser(message.getFromUserId(), offlineMessage);
+        }
+    }
+
+    /**
+     * 当新的WebSocket连接建立时调用
+     */
+    @Override
+    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+        log.info("新的连接:{}", ctx.channel().id().asLongText());
+    }
+
+    /**
+     * 当WebSocket连接断开时调用
+     */
+    @Override
+    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+        log.info("连接断开:{}", ctx.channel().id().asLongText());
+        // 清理用户连接
+        webSocketService.removeUserConnection(ctx);
+    }
+
+    /**
+     * 处理异常情况
+     */
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        log.error("WebSocket异常", cause);
+        ctx.close();
+    }
+
+    /**
+     * 生成聊天ID
+     */
+    private String generateChatId(String userId1, String userId2) {
+        // 确保两个用户之间的聊天ID唯一
+        return userId1.compareTo(userId2) < 0 ?
+            userId1 + "_" + userId2 :
+            userId2 + "_" + userId1;
+    }
+}

+ 55 - 0
src/main/java/com/zhentao/information/netty/NettyServer.java

@@ -0,0 +1,55 @@
+package com.zhentao.information.netty;
+
+import com.zhentao.information.handler.WebSocketHandler;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+
+/**
+ * Netty服务器启动类
+ * 启动动一个基于 Netty 的 WebSocket 服务器
+ * 启动 WebSocket 服务器:
+ * 它会配置一个 Netty 的 ServerBootstrap,并绑定到指定的端口(默认是 8888)。
+ * 它会初始化一个 Netty 的通道管道(ChannelPipeline),并添加各种处理器来处理 WebSocket 连接和消息。
+ */
+@Slf4j
+@Component
+public class NettyServer {
+
+    @Value("${netty.port:8888}")
+    private int port;
+
+    @Resource
+    private ServerBootstrap serverBootstrap;
+
+    @Resource
+    private WebSocketHandler webSocketHandler;
+
+    @PostConstruct
+    public void start() throws Exception {
+        serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
+            @Override
+            protected void initChannel(SocketChannel ch) {
+                ch.pipeline()
+                        .addLast(new HttpServerCodec())
+                        .addLast(new ChunkedWriteHandler())
+                        .addLast(new HttpObjectAggregator(65536))
+                        .addLast(new WebSocketServerProtocolHandler("/ws"))
+                        .addLast(webSocketHandler);
+            }
+        });
+
+        serverBootstrap.bind(port).sync();
+        log.info("Netty服务器启动成功,端口:{}", port);
+    }
+}

+ 29 - 0
src/main/java/com/zhentao/information/repository/ChatMessageRepository.java

@@ -0,0 +1,29 @@
+package com.zhentao.information.repository;
+
+import com.zhentao.information.entity.ChatMessage;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * 聊天消息仓库
+ */
+@Repository
+public interface ChatMessageRepository extends MongoRepository<ChatMessage, String> {
+
+    /**
+     * 查询两个用户之间的聊天记录
+     */
+    List<ChatMessage> findByChatId(String chatId);
+
+    /**
+     * 查询用户的所有未读消息
+     */
+    List<ChatMessage> findByToUserIdAndIsReadFalse(String toUserId);
+
+    /**
+     * 查询toUserId为某个群ID的所有消息
+     */
+    List<ChatMessage> findByToUserId(String toUserId);
+}

+ 57 - 0
src/main/java/com/zhentao/information/service/GroupInitService.java

@@ -0,0 +1,57 @@
+package com.zhentao.information.service;
+
+import com.zhentao.groups.dto.GroupDto;
+import com.zhentao.groups.service.GroupsService;
+import com.zhentao.information.cache.GroupMemberCache;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 群聊初始化服务
+ * 用于初始化群成员缓存
+ */
+@Slf4j
+@Service
+public class GroupInitService {
+
+    @Resource
+    private GroupsService groupsService;
+
+    @Resource
+    private GroupMemberCache groupMemberCache;
+
+    /**
+     * 在应用启动时初始化群成员缓存
+     */
+    @PostConstruct
+    public void init() {
+        try {
+            List<GroupDto> groupList = groupsService.getList();
+            groupList.forEach(group -> {
+                groupMemberCache.updateGroupMembers(group.getGroupId(), group.getUid());
+            });
+            log.info("群成员缓存初始化完成,共 {} 个群", groupList.size());
+        } catch (Exception e) {
+            log.error("群成员缓存初始化失败", e);
+        }
+    }
+
+    /**
+     * 刷新群成员缓存
+     */
+    public void refreshGroupMemberCache() {
+        try {
+            List<GroupDto> groupList = groupsService.getList();
+            groupList.forEach(group -> {
+                groupMemberCache.updateGroupMembers(group.getGroupId(), group.getUid());
+            });
+            log.info("群成员缓存刷新完成,共 {} 个群", groupList.size());
+        } catch (Exception e) {
+            log.error("群成员缓存刷新失败", e);
+        }
+    }
+} 

+ 347 - 0
src/main/java/com/zhentao/information/service/WebSocketService.java

@@ -0,0 +1,347 @@
+package com.zhentao.information.service;
+
+import com.alibaba.fastjson.JSON;
+import com.zhentao.groups.MongoDB.pojo.Message;
+import com.zhentao.information.cache.ChannelCache;
+import com.zhentao.information.cache.GroupChannelCache;
+import com.zhentao.information.cache.GroupMemberCache;
+import com.zhentao.tool.TokenUtils;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.group.ChannelGroup;
+import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+import com.zhentao.groups.service.GroupsService;
+import com.zhentao.groups.dto.GroupDto;
+import com.zhentao.information.entity.ChatMessage;
+import com.zhentao.information.repository.ChatMessageRepository;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * WebSocket服务类
+ * 处理WebSocket连接、消息发送等业务逻辑
+ */
+@Slf4j
+@Service
+public class WebSocketService {
+
+    @Resource
+    private ChannelCache channelCache;
+
+    @Resource
+    private GroupChannelCache groupChannelCache;
+
+    @Resource
+    private GroupMemberCache groupMemberCache;
+
+    @Autowired
+    @Lazy
+    private GroupsService groupsService;
+
+    @Autowired
+    private ChatMessageRepository chatMessageRepository;
+
+    // 存储用户token的Map
+    private final Map<String, String> userTokenMap = new ConcurrentHashMap<>();
+    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
+
+    /**
+     * 存储用户token
+     * @param userId 用户ID
+     * @param token 用户token
+     */
+    public void storeUserToken(String userId, String token) {
+        userTokenMap.put(userId, token);
+        log.info("用户 {} 的token已存储", userId);
+    }
+
+    /**
+     * 获取用户token
+     * @param userId 用户ID
+     * @return 用户token
+     */
+    public String getUserToken(String userId) {
+        return userTokenMap.get(userId);
+    }
+
+    /**
+     * 处理用户登录
+     * @param token 用户token
+     * @param ctx Channel上下文
+     * @return 用户ID,如果登录失败返回null
+     */
+    public String handleUserLogin(String token, ChannelHandlerContext ctx) {
+        String userId = TokenUtils.getUserIdFromToken(token);
+        if (userId != null) {
+            // 验证token是否与存储的token匹配
+            String storedToken = userTokenMap.get(userId);
+            if (storedToken != null && storedToken.equals(token)) {
+                // 将用户ID和Channel绑定
+                channelCache.addCache(userId, ctx);
+                log.info("用户 {} 连接成功", userId);
+
+                // 发送连接成功消息
+                Message response = new Message();
+                response.setType("connect_success");
+                response.setContent("连接成功");
+                ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(response)));
+                return userId;
+            } else {
+                log.error("用户 {} 的token验证失败", userId);
+                ctx.close();
+            }
+        } else {
+            log.error("无效的token");
+            ctx.close();
+        }
+        return null;
+    }
+
+    /**
+     * 发送消息给指定用户
+     * @param userId 接收者用户ID
+     * @param message 消息内容
+     * @return 是否发送成功
+     */
+    public boolean sendMessageToUser(String userId, Message message) {
+        if (message.getType() == null) {
+            message.setType("text");
+        }
+
+        ChannelHandlerContext ctx = channelCache.getCache(userId);
+        if (ctx != null && ctx.channel().isActive()) {
+            try {
+                String messageJson = JSON.toJSONString(message);
+                log.info("发送消息给用户 {}: {}", userId, messageJson);
+                ctx.channel().writeAndFlush(new TextWebSocketFrame(messageJson));
+                return true;
+            } catch (Exception e) {
+                log.error("发送消息给用户 {} 失败", userId, e);
+                retrySendMessage(userId, message);
+                return false;
+            }
+        } else {
+            log.info("用户 {} 不在线,消息将保存到数据库", userId);
+            // 离线时也保存文件/图片/视频消息到MongoDB
+            if (message.getFileUrl() != null) {
+                ChatMessage chatMessage = new ChatMessage();
+                chatMessage.setFromUserId(message.getFromUserId());
+                chatMessage.setToUserId(userId);
+                chatMessage.setContent(message.getContent());
+                chatMessage.setType(message.getType());
+                chatMessage.setTimestamp(System.currentTimeMillis());
+                chatMessage.setIsRead(false);
+                chatMessage.setChatId(generateChatId(message.getFromUserId(), userId));
+                chatMessage.setFileUrl(message.getFileUrl());
+                chatMessage.setFileName(message.getFileName());
+                chatMessage.setFileType(message.getFileType());
+                chatMessage.setFileSize(message.getFileSize());
+                chatMessage.setAvatar(message.getAvatar());
+//                chatMessageRepository.save(chatMessage);
+            }
+            return false;
+        }
+    }
+
+    private void retrySendMessage(String userId, Message message) {
+        scheduler.schedule(() -> {
+            ChannelHandlerContext ctx = channelCache.getCache(userId);
+            if (ctx != null && ctx.channel().isActive()) {
+                try {
+                    String messageJson = JSON.toJSONString(message);
+                    log.info("重试发送消息给用户 {}: {}", userId, messageJson);
+                    ctx.channel().writeAndFlush(new TextWebSocketFrame(messageJson));
+                } catch (Exception e) {
+                    log.error("重试发送消息给用户 {} 失败", userId, e);
+                }
+            }
+        }, 1, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 广播消息给所有在线用户
+     * @param message 消息内容
+     */
+    public void broadcastMessage(Message message) {
+        channelCache.getAllCache().forEach((userId, ctx) -> {
+            if (ctx.channel().isActive()) {
+                try {
+                    String messageJson = JSON.toJSONString(message);
+                    log.info("广播消息给用户 {}: {}", userId, messageJson);
+                    ctx.channel().writeAndFlush(new TextWebSocketFrame(messageJson));
+                } catch (Exception e) {
+                    log.error("广播消息给用户 {} 失败", userId, e);
+                }
+            }
+        });
+    }
+
+    /**
+     * 检查用户是否在线
+     * @param userId 用户ID
+     * @return 是否在线
+     */
+    public boolean isUserOnline(String userId) {
+        ChannelHandlerContext ctx = channelCache.getCache(userId);
+        return ctx != null && ctx.channel().isActive();
+    }
+
+    /**
+     * 处理群聊消息
+     * @param message 群聊消息
+     * @return 是否发送成功
+     */
+    public boolean handleGroupMessage(Message message) {
+        Long groupId = message.getGroupId();
+        if (groupId == null) {
+            log.error("群聊消息缺少群ID");
+            return false;
+        }
+
+        // 存储群聊消息到MongoDB
+        ChatMessage chatMessage = new ChatMessage();
+        chatMessage.setFromUserId(message.getFromUserId());
+        chatMessage.setToUserId(String.valueOf(groupId));
+        chatMessage.setContent(message.getContent());
+        chatMessage.setType(message.getType() != null ? message.getType() : "group_chat");
+        chatMessage.setTimestamp(System.currentTimeMillis());
+        chatMessage.setIsRead(false);
+        chatMessage.setChatId("group_" + groupId);
+        // 新增文件/图片/视频元数据
+        if (message.getFileUrl() != null) {
+            chatMessage.setFileUrl(message.getFileUrl());
+            chatMessage.setFileName(message.getFileName());
+            chatMessage.setFileType(message.getFileType());
+            chatMessage.setFileSize(message.getFileSize());
+        }
+        chatMessage.setAvatar(message.getAvatar());
+        chatMessageRepository.save(chatMessage);
+
+        // 获取群成员
+        List<GroupDto> groupList = groupsService.getList();
+        List<Long> groupMembers = null;
+        for (GroupDto group : groupList) {
+            if (group.getGroupId().equals(groupId)) {
+                groupMembers = group.getUid();
+                break;
+            }
+        }
+
+        if (groupMembers == null || groupMembers.isEmpty()) {
+            log.error("群 {} 不存在或没有成员", groupId);
+            return false;
+        }
+
+        boolean allSent = true;
+        for (Long memberId : groupMembers) {
+            String memberIdStr = String.valueOf(memberId);
+            if (!memberIdStr.equals(message.getFromUserId())) {
+                ChannelHandlerContext ctx = channelCache.getCache(memberIdStr);
+                if (ctx != null && ctx.channel().isActive()) {
+                    try {
+                        String messageJson = JSON.toJSONString(message);
+                        log.info("发送群消息给用户 {}: {}", memberIdStr, messageJson);
+                        ctx.channel().writeAndFlush(new TextWebSocketFrame(messageJson));
+                    } catch (Exception e) {
+                        log.error("发送群消息给用户 {} 失败", memberId, e);
+                        allSent = false;
+                    }
+                }
+            }
+        }
+        log.info("群 {} 的消息已广播,群成员数:{}", groupId, groupMembers.size());
+        return allSent;
+    }
+
+    /**
+     * 用户登录时,将其加入所有群聊的ChannelGroup
+     * @param userId 用户ID
+     */
+    public void joinAllGroups(String userId) {
+        Long userIdLong = Long.valueOf(userId);
+        Map<Long, List<Long>> allGroups = groupMemberCache.getAllGroupMembers();
+
+        allGroups.forEach((groupId, members) -> {
+            if (members.contains(userIdLong)) {
+                addUserToGroup(groupId, userId);
+            }
+        });
+
+        log.info("用户 {} 已加入所有群聊的ChannelGroup", userId);
+    }
+
+    /**
+     * 将用户添加到群聊ChannelGroup
+     * @param groupId 群ID
+     * @param userId 用户ID
+     * @return 是否添加成功
+     */
+    public boolean addUserToGroup(Long groupId, String userId) {
+        // 验证用户是否在群中
+        if (!groupMemberCache.isUserInGroup(groupId, Long.valueOf(userId))) {
+            log.error("用户 {} 不在群 {} 中", userId, groupId);
+            return false;
+        }
+
+        ChannelHandlerContext ctx = channelCache.getCache(userId);
+        if (ctx == null || !ctx.channel().isActive()) {
+            log.error("用户 {} 不在线", userId);
+            return false;
+        }
+
+        ChannelGroup channelGroup = groupChannelCache.getGroup(groupId);
+        if (channelGroup == null) {
+            channelGroup = groupChannelCache.addGroup(groupId);
+        }
+
+        channelGroup.add(ctx.channel());
+        log.info("用户 {} 已添加到群 {} 的ChannelGroup", userId, groupId);
+        return true;
+    }
+
+    /**
+     * 将用户从群聊ChannelGroup中移除
+     * @param groupId 群ID
+     * @param userId 用户ID
+     * @return 是否移除成功
+     */
+    public boolean removeUserFromGroup(Long groupId, String userId) {
+        ChannelHandlerContext ctx = channelCache.getCache(userId);
+        if (ctx == null || !ctx.channel().isActive()) {
+            return false;
+        }
+
+        ChannelGroup channelGroup = groupChannelCache.getGroup(groupId);
+        if (channelGroup != null) {
+            channelGroup.remove(ctx.channel());
+            log.info("用户 {} 已从群 {} 的ChannelGroup移除", userId, groupId);
+            return true;
+        }
+        return false;
+    }
+
+    public void removeUserConnection(ChannelHandlerContext ctx) {
+        // 从ChannelCache中移除用户连接
+        channelCache.removeCache(ctx);
+        // 从所有群组中移除用户
+        groupChannelCache.getAllGroups().forEach((groupId, channelGroup) -> {
+            channelGroup.remove(ctx.channel());
+        });
+    }
+
+    // 新增:生成聊天ID
+    private String generateChatId(String userId1, String userId2) {
+        return userId1.compareTo(userId2) < 0 ?
+            userId1 + "_" + userId2 :
+            userId2 + "_" + userId1;
+    }
+}

+ 47 - 0
src/main/java/com/zhentao/intercepoter/Userinterceptor.java

@@ -0,0 +1,47 @@
+package com.zhentao.intercepoter;
+
+import com.zhentao.config.NullLogin;
+import com.zhentao.tool.TokenUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Component
+public class Userinterceptor implements HandlerInterceptor {
+    @Autowired
+    private RedisTemplate<String,String> redisTemplate;
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        String token = request.getHeader("token");
+        HandlerMethod handlerMethod = (HandlerMethod) handler;
+        NullLogin annotation = handlerMethod.getMethod().getAnnotation(NullLogin.class);
+        System.err.println("自定义注解"+annotation);
+        System.err.println(request.getRequestURI());
+        if (annotation!=null){
+            return true;
+        }
+        if (token==null){
+            System.err.println("Token不能为空");
+            return false;
+        }
+        String userIdFromToken = TokenUtils.getUserIdFromToken(token);
+        String s = redisTemplate.opsForValue().get(userIdFromToken);
+
+        String userIdFromToken1 = TokenUtils.getUserIdFromToken(s);
+
+        System.err.println(userIdFromToken);
+        System.err.println(userIdFromToken1);
+
+        if (s!=null && userIdFromToken1.equals(userIdFromToken)){
+            return true;
+        }else {
+            System.err.println("Token错误");
+            return false;
+        }
+    }
+}

+ 93 - 0
src/main/java/com/zhentao/moment/controller/MonmentController.java

@@ -0,0 +1,93 @@
+package com.zhentao.moment.controller;
+
+import com.zhentao.moment.dto.CommentsDto;
+import com.zhentao.moment.dto.MonmentDto;
+import com.zhentao.moment.service.UserMomentsService;
+import com.zhentao.tool.TokenUtils;
+import com.zhentao.vo.Result;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.List;
+
+@RestController
+@RequestMapping("monment")
+public class MonmentController {
+    @Autowired
+    private UserMomentsService userMomentsService;
+//    发布朋友圈
+    @RequestMapping("sendMonment")
+    public Result sendMonment(@RequestHeader String  token,@ModelAttribute MonmentDto monmentDto){
+        return userMomentsService.sendMonment(token,monmentDto);
+    }
+//    朋友圈的个人信息
+    @RequestMapping("userinfo")
+    public Result userinfo(@RequestHeader String token){
+        return Result.OK(userMomentsService.userinfo(token),"查询成功");
+    }
+
+//    删除朋友圈
+    @RequestMapping("deleteMonment")
+    public Result deleteMonment(@RequestHeader String token,@RequestBody MonmentDto monmentDto){
+        String uid = TokenUtils.getUserIdFromToken(token);
+        monmentDto.setUid(Long.valueOf(uid));
+        return userMomentsService.deleteMonment(token,monmentDto);
+    }
+//    查看我的朋友圈
+    @RequestMapping("getMyMonment")
+    public Result getMyMonment(@RequestHeader String token){
+        String uid = TokenUtils.getUserIdFromToken(token);
+        return Result.OK(userMomentsService.getMyMonment(Long.valueOf(uid)),"查询成功");
+    }
+//    查看好友的朋友圈
+    @RequestMapping("getFriendMonment")
+    public Result getFriendMonment(@RequestHeader String token){
+        String uid = TokenUtils.getUserIdFromToken(token);
+        return Result.OK(userMomentsService.getFriendMonment(Long.valueOf(uid)),"查询成功");
+    }
+//    点赞接口
+    @RequestMapping(value = "likeMonment",produces = "application/json;charset=UTF-8")
+    public Result likeMonment(@RequestHeader String token,@RequestBody MonmentDto monmentDto){
+        String uid = TokenUtils.getUserIdFromToken(token);
+        monmentDto.setUid(Long.valueOf(uid));
+        return userMomentsService.likeMonment(Long.valueOf(uid),monmentDto);
+    }
+    //  评论朋友圈
+    @RequestMapping(value = "commentMonment",produces = "application/json;charset=UTF-8")
+    public Result commentMonment(@RequestHeader String token, @RequestBody CommentsDto commentsDto){
+        String uid = TokenUtils.getUserIdFromToken(token);
+        commentsDto.setUserId(Long.valueOf(uid));
+        return userMomentsService.commentsMonment(Long.valueOf(uid),commentsDto);
+    }
+    //  查询点赞列表
+    @RequestMapping("likeList")
+    public Result likeList(@RequestHeader String token,@RequestBody MonmentDto monmentDto){
+        String uid = TokenUtils.getUserIdFromToken(token);
+        monmentDto.setUid(Long.valueOf(uid));
+        return userMomentsService.likeList(Long.valueOf(uid),monmentDto);
+    }
+    //  查询评论列表
+    @RequestMapping("commentList")
+    public Result commentList(@RequestHeader String token,@RequestBody CommentsDto commentsDto){
+        String uid = TokenUtils.getUserIdFromToken(token);
+        commentsDto.setUserId(Long.valueOf(uid));
+        return userMomentsService.commentList(Long.valueOf(uid),commentsDto);
+    }
+
+//    合并所有接口,分页查询
+    @RequestMapping("allListPage")
+    public Result allListPage(@RequestHeader String token,@RequestBody MonmentDto monmentDto){
+        String uid = TokenUtils.getUserIdFromToken(token);
+        monmentDto.setUid(Long.valueOf(uid));
+        return userMomentsService.allListPage(Long.valueOf(uid),monmentDto);
+    }
+//    更改朋友圈背景图
+    @RequestMapping("updateBackground")
+    public Result updateBackground(@RequestHeader String token,@RequestParam("file") MultipartFile file) throws IOException {
+        String uid = TokenUtils.getUserIdFromToken(token);
+        return userMomentsService.updateBackground(Long.valueOf(uid),file);
+    }
+
+}

+ 113 - 0
src/main/java/com/zhentao/moment/domain/MomentComments.java

@@ -0,0 +1,113 @@
+package com.zhentao.moment.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+/**
+ * 朋友圈评论表
+ * @TableName moment_comments
+ */
+@TableName(value ="moment_comments")
+@Data
+public class MomentComments implements Serializable {
+    /**
+     *
+     */
+    @TableId(type = IdType.AUTO)
+    private Long commentId;
+
+    /**
+     * 动态ID
+     */
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long momentId;
+
+    /**
+     * 评论用户ID
+     */
+    private Long userId;
+
+    /**
+     * 评论内容
+     */
+    private String content;
+
+    /**
+     * 回复的评论ID
+     */
+    private Long replyTo;
+
+    /**
+     * 回复的用户ID
+     */
+    private Long replyToUser;
+
+    /**
+     *
+     */
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createdAt;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        MomentComments other = (MomentComments) that;
+        return (this.getCommentId() == null ? other.getCommentId() == null : this.getCommentId().equals(other.getCommentId()))
+            && (this.getMomentId() == null ? other.getMomentId() == null : this.getMomentId().equals(other.getMomentId()))
+            && (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId()))
+            && (this.getContent() == null ? other.getContent() == null : this.getContent().equals(other.getContent()))
+            && (this.getReplyTo() == null ? other.getReplyTo() == null : this.getReplyTo().equals(other.getReplyTo()))
+            && (this.getReplyToUser() == null ? other.getReplyToUser() == null : this.getReplyToUser().equals(other.getReplyToUser()))
+            && (this.getCreatedAt() == null ? other.getCreatedAt() == null : this.getCreatedAt().equals(other.getCreatedAt()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getCommentId() == null) ? 0 : getCommentId().hashCode());
+        result = prime * result + ((getMomentId() == null) ? 0 : getMomentId().hashCode());
+        result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
+        result = prime * result + ((getContent() == null) ? 0 : getContent().hashCode());
+        result = prime * result + ((getReplyTo() == null) ? 0 : getReplyTo().hashCode());
+        result = prime * result + ((getReplyToUser() == null) ? 0 : getReplyToUser().hashCode());
+        result = prime * result + ((getCreatedAt() == null) ? 0 : getCreatedAt().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", commentId=").append(commentId);
+        sb.append(", momentId=").append(momentId);
+        sb.append(", userId=").append(userId);
+        sb.append(", content=").append(content);
+        sb.append(", replyTo=").append(replyTo);
+        sb.append(", replyToUser=").append(replyToUser);
+        sb.append(", createdAt=").append(createdAt);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 91 - 0
src/main/java/com/zhentao/moment/domain/MomentLikes.java

@@ -0,0 +1,91 @@
+package com.zhentao.moment.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+/**
+ * 朋友圈点赞表
+ * @TableName moment_likes
+ */
+@TableName(value ="moment_likes")
+@Data
+public class MomentLikes implements Serializable {
+    /**
+     *
+     */
+    @TableId(type = IdType.AUTO)
+    private Long likeId;
+
+    /**
+     * 动态ID
+     */
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long momentId;
+
+    /**
+     * 点赞用户ID
+     */
+    private Long userId;
+
+    /**
+     *
+     */
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createdAt;
+    @TableField(exist = false)
+    private Boolean isLiked; // 当前用户是否点赞
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        MomentLikes other = (MomentLikes) that;
+        return (this.getLikeId() == null ? other.getLikeId() == null : this.getLikeId().equals(other.getLikeId()))
+            && (this.getMomentId() == null ? other.getMomentId() == null : this.getMomentId().equals(other.getMomentId()))
+            && (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId()))
+            && (this.getCreatedAt() == null ? other.getCreatedAt() == null : this.getCreatedAt().equals(other.getCreatedAt()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getLikeId() == null) ? 0 : getLikeId().hashCode());
+        result = prime * result + ((getMomentId() == null) ? 0 : getMomentId().hashCode());
+        result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
+        result = prime * result + ((getCreatedAt() == null) ? 0 : getCreatedAt().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", likeId=").append(likeId);
+        sb.append(", momentId=").append(momentId);
+        sb.append(", userId=").append(userId);
+        sb.append(", createdAt=").append(createdAt);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 99 - 0
src/main/java/com/zhentao/moment/domain/MomentMedias.java

@@ -0,0 +1,99 @@
+package com.zhentao.moment.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import lombok.Data;
+
+/**
+ * 朋友圈媒体表
+ * @TableName moment_medias
+ */
+@TableName(value ="moment_medias")
+@Data
+public class MomentMedias implements Serializable {
+    /**
+     *
+     */
+    @TableId(type = IdType.AUTO)
+    private Long mediaId;
+
+    /**
+     * 动态ID
+     */
+//    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    // 使用JsonSerialize指定序列化方式为字符串
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long momentId;
+
+    /**
+     * 资源URL
+     */
+    private String mediaUrl;
+
+    /**
+     * 类型(1-图片,2-视频)
+     */
+    private Integer mediaType;
+
+    /**
+     * 排序
+     */
+    private Integer sortOrder;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        MomentMedias other = (MomentMedias) that;
+        return (this.getMediaId() == null ? other.getMediaId() == null : this.getMediaId().equals(other.getMediaId()))
+            && (this.getMomentId() == null ? other.getMomentId() == null : this.getMomentId().equals(other.getMomentId()))
+            && (this.getMediaUrl() == null ? other.getMediaUrl() == null : this.getMediaUrl().equals(other.getMediaUrl()))
+            && (this.getMediaType() == null ? other.getMediaType() == null : this.getMediaType().equals(other.getMediaType()))
+            && (this.getSortOrder() == null ? other.getSortOrder() == null : this.getSortOrder().equals(other.getSortOrder()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getMediaId() == null) ? 0 : getMediaId().hashCode());
+        result = prime * result + ((getMomentId() == null) ? 0 : getMomentId().hashCode());
+        result = prime * result + ((getMediaUrl() == null) ? 0 : getMediaUrl().hashCode());
+        result = prime * result + ((getMediaType() == null) ? 0 : getMediaType().hashCode());
+        result = prime * result + ((getSortOrder() == null) ? 0 : getSortOrder().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", mediaId=").append(mediaId);
+        sb.append(", momentId=").append(momentId);
+        sb.append(", mediaUrl=").append(mediaUrl);
+        sb.append(", mediaType=").append(mediaType);
+        sb.append(", sortOrder=").append(sortOrder);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 127 - 0
src/main/java/com/zhentao/moment/domain/UserMoments.java

@@ -0,0 +1,127 @@
+package com.zhentao.moment.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.zhentao.user.domain.UserLogin;
+import lombok.Data;
+
+/**
+ * 朋友圈动态表
+ * @TableName user_moments
+ */
+@TableName(value ="user_moments")
+@Data
+public class UserMoments implements Serializable {
+    /**
+     *
+     */
+    @TableId(type = IdType.AUTO)
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long momentId;
+
+    /**
+     * 发布者ID
+     */
+    private Long userId;
+
+    /**
+     * 动态内容
+     */
+    private String content;
+
+    /**
+     * 内容类型(1-文本,2-图片,3-视频)
+     */
+    private Integer contentType;
+    private String url;
+    private String filename;
+    private Long filesize;
+    /**
+     * 可见性(0-公开,1-仅好友,2-私密)
+     */
+    private Integer visibility;
+
+    /**
+     * 位置信息
+     */
+    private String location;
+
+    /**
+     *
+     */
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
+
+    private Date createdAt;
+
+    /**
+     *
+     */
+    private Date updatedAt;
+
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        UserMoments other = (UserMoments) that;
+        return (this.getMomentId() == null ? other.getMomentId() == null : this.getMomentId().equals(other.getMomentId()))
+            && (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId()))
+            && (this.getContent() == null ? other.getContent() == null : this.getContent().equals(other.getContent()))
+            && (this.getContentType() == null ? other.getContentType() == null : this.getContentType().equals(other.getContentType()))
+            && (this.getVisibility() == null ? other.getVisibility() == null : this.getVisibility().equals(other.getVisibility()))
+            && (this.getLocation() == null ? other.getLocation() == null : this.getLocation().equals(other.getLocation()))
+            && (this.getCreatedAt() == null ? other.getCreatedAt() == null : this.getCreatedAt().equals(other.getCreatedAt()))
+            && (this.getUpdatedAt() == null ? other.getUpdatedAt() == null : this.getUpdatedAt().equals(other.getUpdatedAt()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getMomentId() == null) ? 0 : getMomentId().hashCode());
+        result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
+        result = prime * result + ((getContent() == null) ? 0 : getContent().hashCode());
+        result = prime * result + ((getContentType() == null) ? 0 : getContentType().hashCode());
+        result = prime * result + ((getVisibility() == null) ? 0 : getVisibility().hashCode());
+        result = prime * result + ((getLocation() == null) ? 0 : getLocation().hashCode());
+        result = prime * result + ((getCreatedAt() == null) ? 0 : getCreatedAt().hashCode());
+        result = prime * result + ((getUpdatedAt() == null) ? 0 : getUpdatedAt().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", momentId=").append(momentId);
+        sb.append(", userId=").append(userId);
+        sb.append(", content=").append(content);
+        sb.append(", contentType=").append(contentType);
+        sb.append(", visibility=").append(visibility);
+        sb.append(", location=").append(location);
+        sb.append(", createdAt=").append(createdAt);
+        sb.append(", updatedAt=").append(updatedAt);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 20 - 0
src/main/java/com/zhentao/moment/dto/CommentsDto.java

@@ -0,0 +1,20 @@
+package com.zhentao.moment.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class CommentsDto {
+
+    private Long commentId;
+    private Long userId;
+    private String momentId;
+    private String content;
+    private Long replyTo;//回复的朋友圈id
+    private Long replyToUser;//回复的用户id
+
+    //存储用户昵称
+    private List<String> userUsernames;
+}

+ 24 - 0
src/main/java/com/zhentao/moment/dto/MonmentDto.java

@@ -0,0 +1,24 @@
+package com.zhentao.moment.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+public class MonmentDto  {
+
+//    private List<Long> momentIds;
+    private Integer currentPage;
+    private Integer pageSize;
+    private String momentId;
+    private Long uid;
+    private Integer visible;//是否可见  1公开  2好友 3自己
+    private String content;//发布内容
+//    private MultipartFile urlImage;
+    private Integer contentType;//文章类型
+    private String location;
+    private List<MultipartFile> files;
+}

+ 41 - 0
src/main/java/com/zhentao/moment/enums/ContentTypeEnum.java

@@ -0,0 +1,41 @@
+package com.zhentao.moment.enums;
+
+public enum ContentTypeEnum {
+    TEXT(1, "文本"),
+    IMAGE(2, "图片"),
+    VIDEO(3, "视频");
+    private final Integer code;
+    private final String desc;
+
+    ContentTypeEnum(Integer code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+    public static ContentTypeEnum getByCodeOrThrow(int code) {
+        for (ContentTypeEnum e : values()) {
+            if (e.code == code) {
+                return e;
+            }
+        }
+        throw new IllegalArgumentException("不支持的内容类型:" + code);
+    }
+    // 根据 code 获取枚举
+//    public static ContentTypeEnum getByCode(Integer code) {
+//        for (ContentTypeEnum type : values()) {
+//            if (type.code.equals(code)) {
+//                return type;
+//            }
+//        }
+//        // 不存在时可抛异常或返回默认值,根据业务决定
+//        throw new IllegalArgumentException("无效的 content_type: " + code);
+//    }
+//
+//    // getter
+//    public Integer getCode() {
+//        return code;
+//    }
+//
+//    public String getDesc() {
+//        return desc;
+//    }
+}

+ 18 - 0
src/main/java/com/zhentao/moment/mapper/MomentCommentsMapper.java

@@ -0,0 +1,18 @@
+package com.zhentao.moment.mapper;
+
+import com.zhentao.moment.domain.MomentComments;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author 86159
+* @description 针对表【moment_comments(朋友圈评论表)】的数据库操作Mapper
+* @createDate 2025-06-04 11:56:59
+* @Entity com.zhentao.moment.domain.MomentComments
+*/
+public interface MomentCommentsMapper extends BaseMapper<MomentComments> {
+
+}
+
+
+
+

+ 18 - 0
src/main/java/com/zhentao/moment/mapper/MomentLikesMapper.java

@@ -0,0 +1,18 @@
+package com.zhentao.moment.mapper;
+
+import com.zhentao.moment.domain.MomentLikes;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author 86159
+* @description 针对表【moment_likes(朋友圈点赞表)】的数据库操作Mapper
+* @createDate 2025-06-04 11:56:59
+* @Entity com.zhentao.moment.domain.MomentLikes
+*/
+public interface MomentLikesMapper extends BaseMapper<MomentLikes> {
+
+}
+
+
+
+

+ 18 - 0
src/main/java/com/zhentao/moment/mapper/MomentMediasMapper.java

@@ -0,0 +1,18 @@
+package com.zhentao.moment.mapper;
+
+import com.zhentao.moment.domain.MomentMedias;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author 86159
+* @description 针对表【moment_medias(朋友圈媒体表)】的数据库操作Mapper
+* @createDate 2025-06-04 11:56:59
+* @Entity com.zhentao.moment.domain.MomentMedias
+*/
+public interface MomentMediasMapper extends BaseMapper<MomentMedias> {
+
+}
+
+
+
+

+ 18 - 0
src/main/java/com/zhentao/moment/mapper/UserMomentsMapper.java

@@ -0,0 +1,18 @@
+package com.zhentao.moment.mapper;
+
+import com.zhentao.moment.domain.UserMoments;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author 86159
+* @description 针对表【user_moments(朋友圈动态表)】的数据库操作Mapper
+* @createDate 2025-06-04 11:56:59
+* @Entity com.zhentao.moment.domain.UserMoments
+*/
+public interface UserMomentsMapper extends BaseMapper<UserMoments> {
+
+}
+
+
+
+

+ 13 - 0
src/main/java/com/zhentao/moment/service/MomentCommentsService.java

@@ -0,0 +1,13 @@
+package com.zhentao.moment.service;
+
+import com.zhentao.moment.domain.MomentComments;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author 86159
+* @description 针对表【moment_comments(朋友圈评论表)】的数据库操作Service
+* @createDate 2025-06-04 11:56:59
+*/
+public interface MomentCommentsService extends IService<MomentComments> {
+
+}

+ 13 - 0
src/main/java/com/zhentao/moment/service/MomentLikesService.java

@@ -0,0 +1,13 @@
+package com.zhentao.moment.service;
+
+import com.zhentao.moment.domain.MomentLikes;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author 86159
+* @description 针对表【moment_likes(朋友圈点赞表)】的数据库操作Service
+* @createDate 2025-06-04 11:56:59
+*/
+public interface MomentLikesService extends IService<MomentLikes> {
+
+}

+ 13 - 0
src/main/java/com/zhentao/moment/service/MomentMediasService.java

@@ -0,0 +1,13 @@
+package com.zhentao.moment.service;
+
+import com.zhentao.moment.domain.MomentMedias;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author 86159
+* @description 针对表【moment_medias(朋友圈媒体表)】的数据库操作Service
+* @createDate 2025-06-04 11:56:59
+*/
+public interface MomentMediasService extends IService<MomentMedias> {
+
+}

+ 42 - 0
src/main/java/com/zhentao/moment/service/UserMomentsService.java

@@ -0,0 +1,42 @@
+package com.zhentao.moment.service;
+
+import com.zhentao.moment.domain.UserMoments;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhentao.moment.dto.CommentsDto;
+import com.zhentao.moment.dto.MonmentDto;
+import com.zhentao.vo.Result;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+* @author 86159
+* @description 针对表【user_moments(朋友圈动态表)】的数据库操作Service
+* @createDate 2025-06-04 11:56:59
+*/
+public interface UserMomentsService extends IService<UserMoments> {
+    //  发布朋友圈
+    Result sendMonment(String  token,MonmentDto monmentDto);
+    //  删除朋友圈
+    Result deleteMonment(String token,MonmentDto monmentDto);
+    //  获取用户信息
+    Result userinfo(String token);
+    //  查看我的朋友圈
+    Result getMyMonment(Long userId);
+    //  查看好友的朋友圈
+    Result getFriendMonment(Long userId);
+   //   点赞朋友圈
+    Result likeMonment(Long uid, MonmentDto monmentDto);
+    //  评论朋友圈
+    Result commentsMonment(Long userId, CommentsDto commentsDto);
+    //  查询点赞列表
+    Result likeList(Long uid, MonmentDto monmentDto);
+    //  查询评论列表
+    Result commentList(Long valueOf, CommentsDto commentsDto);
+    //    更改朋友圈背景图
+    Result updateBackground(Long userId, MultipartFile file) throws IOException;
+//    查询全部列表
+    Result allListPage(Long userId, MonmentDto monmentDto);
+}

+ 22 - 0
src/main/java/com/zhentao/moment/service/impl/MomentCommentsServiceImpl.java

@@ -0,0 +1,22 @@
+package com.zhentao.moment.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.moment.domain.MomentComments;
+import com.zhentao.moment.service.MomentCommentsService;
+import com.zhentao.moment.mapper.MomentCommentsMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author 86159
+* @description 针对表【moment_comments(朋友圈评论表)】的数据库操作Service实现
+* @createDate 2025-06-04 11:56:59
+*/
+@Service
+public class MomentCommentsServiceImpl extends ServiceImpl<MomentCommentsMapper, MomentComments>
+    implements MomentCommentsService{
+
+}
+
+
+
+

+ 22 - 0
src/main/java/com/zhentao/moment/service/impl/MomentLikesServiceImpl.java

@@ -0,0 +1,22 @@
+package com.zhentao.moment.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.moment.domain.MomentLikes;
+import com.zhentao.moment.service.MomentLikesService;
+import com.zhentao.moment.mapper.MomentLikesMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author 86159
+* @description 针对表【moment_likes(朋友圈点赞表)】的数据库操作Service实现
+* @createDate 2025-06-04 11:56:59
+*/
+@Service
+public class MomentLikesServiceImpl extends ServiceImpl<MomentLikesMapper, MomentLikes>
+    implements MomentLikesService{
+
+}
+
+
+
+

+ 22 - 0
src/main/java/com/zhentao/moment/service/impl/MomentMediasServiceImpl.java

@@ -0,0 +1,22 @@
+package com.zhentao.moment.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.moment.domain.MomentMedias;
+import com.zhentao.moment.service.MomentMediasService;
+import com.zhentao.moment.mapper.MomentMediasMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author 86159
+* @description 针对表【moment_medias(朋友圈媒体表)】的数据库操作Service实现
+* @createDate 2025-06-04 11:56:59
+*/
+@Service
+public class MomentMediasServiceImpl extends ServiceImpl<MomentMediasMapper, MomentMedias>
+    implements MomentMediasService{
+
+}
+
+
+
+

+ 772 - 0
src/main/java/com/zhentao/moment/service/impl/UserMomentsServiceImpl.java

@@ -0,0 +1,772 @@
+package com.zhentao.moment.service.impl;
+
+import com.aliyun.oss.OSSClient;
+import com.aliyun.oss.model.ObjectMetadata;
+import com.aliyun.oss.model.PutObjectRequest;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.moment.domain.MomentComments;
+import com.zhentao.moment.domain.MomentLikes;
+import com.zhentao.moment.domain.UserMoments;
+import com.zhentao.moment.dto.CommentsDto;
+import com.zhentao.moment.dto.MonmentDto;
+import com.zhentao.moment.enums.ContentTypeEnum;
+import com.zhentao.moment.mapper.MomentCommentsMapper;
+import com.zhentao.moment.mapper.MomentLikesMapper;
+import com.zhentao.moment.service.UserMomentsService;
+import com.zhentao.moment.mapper.UserMomentsMapper;
+import com.zhentao.osspicture.OssConfig;
+import com.zhentao.osspicture.OssUtil;
+import com.zhentao.tool.TokenUtils;
+import com.zhentao.user.domain.UserLogin;
+import com.zhentao.user.mapper.UserLoginMapper;
+import com.zhentao.userRelationships.domain.UserRelationships;
+import com.zhentao.userRelationships.mapper.UserRelationshipsMapper;
+import com.zhentao.utils.SensitiveWordFilter;
+import com.zhentao.utils.SnowflakeUtil;
+import com.zhentao.vo.Result;
+import io.jsonwebtoken.Claims;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+* @author 86159
+* @description 针对表【user_moments(朋友圈动态表)】的数据库操作Service实现
+* @createDate 2025-06-04 11:56:59
+*/
+@Service
+@Slf4j
+public class UserMomentsServiceImpl extends ServiceImpl<UserMomentsMapper, UserMoments>
+    implements UserMomentsService{
+    @Autowired
+    private UserMomentsMapper userMomentsMapper;
+    @Autowired
+    private UserLoginMapper userLoginMapper;
+    @Autowired
+    private MomentLikesMapper momentLikesMapper;
+    @Autowired
+    private OssUtil ossUtil;
+    @Autowired
+    private OssConfig ossConfig;
+    @Autowired
+    private UserRelationshipsMapper userRelationshipsMapper;
+    @Autowired
+    private MomentCommentsMapper momentCommentsMapper;
+    //  发送朋友圈
+    @Override
+    public Result sendMonment(String token,MonmentDto monmentDto) {
+        if (monmentDto.getContent()==null){
+            return Result.error(400,"请输入内容");
+        }
+        if (monmentDto.getContentType()==null){
+            return Result.error(400,"内容类型不能为空");
+        }
+        try {
+            UserMoments userMoments=new UserMoments();
+            userMoments.setMomentId(SnowflakeUtil.nextId());
+            String userId = TokenUtils.getUserIdFromToken(token);
+            userMoments.setUserId(Long.valueOf(userId));
+            String filter = SensitiveWordFilter.filter(monmentDto.getContent());
+            userMoments.setContent(filter);
+            Integer typeCode = monmentDto.getContentType();
+            System.err.println(typeCode);
+            ContentTypeEnum type = ContentTypeEnum.getByCodeOrThrow(typeCode);
+            userMoments.setContentType(typeCode);
+            if (monmentDto.getFiles()!=null && !monmentDto.getFiles().isEmpty()){
+//                if (type!=ContentTypeEnum.IMAGE){
+//                    return Result.error(400,"只有图片类型支持上传多张");
+//                }
+
+            switch (type){
+                case IMAGE:
+                    for (MultipartFile file : monmentDto.getFiles()){
+                        if (file.isEmpty()){
+                            return Result.error(400,"图片不能为空");
+                        }
+                        validateImageContent(file);
+                    }
+                    List<String> imageUrls = new ArrayList<>();
+                    List<String> imageNames = new ArrayList<>();
+                    Long totalSize = 0L;
+                    String imageDir = ossConfig.getUpload().getImage().getDir();
+                    for (MultipartFile file : monmentDto.getFiles()){
+                        String imageFileName = userId + "/" + System.currentTimeMillis() +
+                                getFileExtension(file.getOriginalFilename());
+                        String ossFilePath = imageDir + imageFileName;
+//                        上传OSS
+                        String url = uploadToOss(file, ossFilePath);
+                        imageUrls.add(url);
+                        imageNames.add(file.getOriginalFilename());
+                        totalSize+=file.getSize();
+                    }
+                    String urlJson = "[" + imageUrls.stream()
+                            .map(url -> "\"" + url.replace("\"", "\\\"") + "\"") // 确保URL中的双引号被转义
+                            .collect(Collectors.joining(",")) + "]";
+                    userMoments.setUrl(urlJson);
+                    String nameJson = "[" + imageNames.stream()
+                            .map(name -> "\"" + name.replace("\"", "\\\"") + "\"")
+                            .collect(Collectors.joining(",")) + "]";
+                    userMoments.setFilename(nameJson);
+                    userMoments.setFilesize(totalSize);
+
+                    break;
+                case VIDEO:
+                    MultipartFile videoFile = monmentDto.getFiles().get(0);
+                    if (videoFile.isEmpty()){
+                        return Result.error(400,"视频不能为空");
+                    }
+                    validateVideoContent(videoFile);
+                    String videoDir = ossConfig.getUpload().getVideo().getDir();
+                    String videoFileName = userId + "/" + System.currentTimeMillis() +
+                            getFileExtension(videoFile.getOriginalFilename());
+                    String ossFilePath = videoDir + videoFileName;
+                    String url = uploadToOss(videoFile, ossFilePath);
+
+                    userMoments.setUrl(url);
+
+                    userMoments.setFilename(videoFile.getOriginalFilename());
+                    userMoments.setFilesize(videoFile.getSize());
+                    break;
+                default:
+                return Result.error(400,"内容类型错误");
+                }
+            }
+            else if (type!=ContentTypeEnum.TEXT){
+                return Result.ERR(null,"类型上传不正确");
+            }
+////            userMoments.setContentType();
+//            userMoments.setContent(monmentDto.getContent());
+            userMoments.setVisibility(monmentDto.getVisible());
+            userMoments.setLocation(monmentDto.getLocation());
+            userMoments.setMomentId(SnowflakeUtil.nextId());
+            userMomentsMapper.insert(userMoments);
+            Map<String,Object> map=new HashMap<>();
+            map.put("monmentId",userMoments.getMomentId());
+            map.put("ossFilePath",userMoments.getUrl());
+            return Result.OK(map,"发送成功");
+        }catch (Exception e){
+            e.printStackTrace();
+            return Result.error(500,"发送失败");
+        }
+
+    }
+   //   删除朋友圈
+    @Override
+    public Result deleteMonment(String token,MonmentDto monmentDto) {
+        if (monmentDto.getMomentId()==null){
+        return Result.error(400,"朋友圈id不能为空");
+        }
+        Long userId = Long.valueOf(TokenUtils.getUserIdFromToken(token));
+        UserMoments userMoments = userMomentsMapper.selectById(monmentDto.getMomentId());
+        if (userMoments.getMomentId()==null){
+            return Result.error(400,"没有此朋友圈");
+        }else{
+            if (!userId.equals(userMoments.getUserId())){
+                return Result.error(400,"没有权限");
+            }
+            userMomentsMapper.deleteById(monmentDto.getMomentId());
+            return Result.ERR(200,"删除成功");
+        }
+
+    }
+   //  查询个人信息
+    @Override
+    public Result userinfo(String token) {
+        String userId = TokenUtils.getUserIdFromToken(token);
+        if (userId==null){
+            return Result.error(400,"没有userid");
+        }
+        UserLogin userLogin = userLoginMapper.selectById(userId);
+        return Result.OK(userLogin,"查询成功");
+    }
+   //   查询我的朋友圈
+    @Override
+    public Result getMyMonment(Long userId) {
+        QueryWrapper<UserMoments> queryWrapper=new QueryWrapper<>();
+        queryWrapper.eq("user_id",userId);
+        queryWrapper.orderByDesc("created_at");
+        UserLogin userLogin = userLoginMapper.selectById(userId);
+        List<UserMoments> userMoments = userMomentsMapper.selectList(queryWrapper);
+        Map<String,Object> map=new HashMap<>();
+        map.put("userMoments",userMoments);
+        map.put("userLogin",userLogin);
+        return Result.OK(map,"查询成功");
+    }
+   //  查询好友的朋友圈
+    @Override
+    public Result getFriendMonment(Long userId) {
+        log.info("查询好友朋友圈,用户ID: {}", userId);
+
+        // 构建查询条件,只查询user_id=userId的好友关系
+        QueryWrapper<UserRelationships> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("user_id", userId)
+                .eq("status", 1)
+                .eq("is_blacklist", 0)
+                .eq("is_del", 0)
+                .eq("is_moments", 0)
+                .orderByDesc("created_at");
+
+        // 查询好友关系
+        List<UserRelationships> userRelationships = userRelationshipsMapper.selectList(queryWrapper);
+        log.info("查询到的好友关系数量: {}", userRelationships.size());
+
+        // 调试:打印查询到的每条好友关系
+        for (UserRelationships rel : userRelationships) {
+            log.info("好友关系: user_id={}, friend_id={}, status={}, is_blacklist={}, is_del={}, is_moments={}",
+                    rel.getUserId(), rel.getFriendId(), rel.getStatus(),
+                    rel.getIsBlacklist(), rel.getIsDel(), rel.getIsMoments());
+        }
+
+        // 提取好友ID(单向查询:直接从friend_id获取)
+        List<Long> friendIds = userRelationships.stream()
+                .map(UserRelationships::getFriendId)
+                .collect(Collectors.toList());
+
+        // 处理无好友情况
+        if (friendIds.isEmpty()) {
+            log.warn("没有找到好友,返回空结果");
+            return Result.OK(new ArrayList<>(), "没有好友的朋友圈");
+        }
+
+        // 查询好友的朋友圈
+        QueryWrapper<UserMoments> momentQuery = new QueryWrapper<>();
+        momentQuery.in("user_id", friendIds)
+                .ne("visibility",2)
+                .orderByDesc("created_at");
+        List<UserMoments> userMoments = userMomentsMapper.selectList(momentQuery);
+
+        // 关联用户信息
+        // 1. 提取朋友圈中所有不同的userId
+        Set<Long> userIdsInMoments = userMoments.stream()
+                .map(UserMoments::getUserId)
+                .collect(Collectors.toSet());
+
+        // 2. 根据userId列表查询用户信息
+        if (userIdsInMoments.isEmpty()) {
+            log.warn("好友没有发布任何朋友圈");
+            Map<String, Object> map = new HashMap<>();
+            map.put("userMoments", Collections.emptyList());
+            map.put("userList", Collections.emptyList());
+            return Result.OK(map, "查询成功");
+        }
+
+        QueryWrapper<UserLogin> userQuery = new QueryWrapper<>();
+        userQuery.in("id", userIdsInMoments);
+        List<UserLogin> users = userLoginMapper.selectList(userQuery);
+
+        // 3. 将用户信息转换为Map,根据userId查找
+        Map<Long, UserLogin> userMap = users.stream()
+                .collect(Collectors.toMap(UserLogin::getId, user -> user));
+
+        // 4. 包含用户信息的列表
+        List<Map<String, Object>> userList = userMoments.stream()
+                .map(moment -> {
+                    Map<String, Object> momentMap = new HashMap<>();
+                    BeanUtils.copyProperties(moment, momentMap);
+
+                    UserLogin user = userMap.get(moment.getUserId());
+                    if (user != null) {
+                        momentMap.put("nickName", user.getNickName());
+                        momentMap.put("avatar", user.getAvatar());
+                    }
+                    return momentMap;
+                })
+                .collect(Collectors.toList());
+
+        log.info("查询到的朋友圈数量: {}", userMoments.size());
+        Map<String, Object> map = new HashMap<>();
+        map.put("userMoments", userMoments);
+        map.put("userList", userList);
+        return Result.OK(map, "查询成功");
+    }
+   //  点赞朋友圈
+    @Override
+    public Result likeMonment(Long uid, MonmentDto monmentDto) {
+        System.err.println("赞"+monmentDto.getMomentId());
+        if (monmentDto.getMomentId()==null){
+            return Result.error(400,"请选择要点赞的朋友圈");
+        }
+        QueryWrapper<MomentLikes> queryWrapper=new QueryWrapper<>();
+        queryWrapper.eq("moment_id",monmentDto.getMomentId());
+        queryWrapper.eq("user_id",uid);
+        //  查询是否已点赞
+        MomentLikes momentLikes = momentLikesMapper.selectOne(queryWrapper);
+        if (momentLikes==null){
+         //  没有点赞
+         MomentLikes momentLikes1=new MomentLikes();
+         momentLikes1.setLikeId(SnowflakeUtil.nextId());
+         momentLikes1.setMomentId(Long.valueOf(monmentDto.getMomentId()));
+         momentLikes1.setUserId(uid);
+         momentLikesMapper.insert(momentLikes1);
+         return Result.OK(200,"点赞成功");
+        }else{
+        //   已点赞
+            momentLikesMapper.delete(queryWrapper);
+            return Result.error(400,"取消点赞");
+        }
+    }
+    //  评论朋友圈
+    @Override
+    public Result commentsMonment(Long userId, CommentsDto commentsDto) {
+        System.err.println("轮"+commentsDto.getMomentId());
+        MomentComments momentComments = new MomentComments();
+        momentComments.setUserId(userId);
+        momentComments.setCommentId(SnowflakeUtil.nextId());
+        UserMoments userMoments = userMomentsMapper.selectById(commentsDto.getMomentId());
+        System.err.println(userMoments);
+        if (userMoments==null){
+            return Result.error(400,"朋友圈不存在");
+        }
+        if (commentsDto.getContent()==null){
+            return Result.error(400,"评论不能为空");
+        }
+        commentsDto.setReplyTo(Long.valueOf(commentsDto.getMomentId()));
+        commentsDto.setReplyToUser(userMoments.getUserId());
+        momentComments.setMomentId(Long.valueOf(commentsDto.getMomentId()));
+        momentComments.setContent(commentsDto.getContent());
+        momentComments.setReplyToUser(commentsDto.getReplyToUser());
+        momentComments.setReplyTo(commentsDto.getReplyTo());
+        momentCommentsMapper.insert(momentComments);
+        return Result.OK(200,"评论成功");
+    }
+    //  查询点赞列表
+    @Override
+    public Result likeList(Long uid, MonmentDto monmentDto) {
+        if (monmentDto.getMomentId()==null){
+            return Result.error(400,"朋友圈id不能为空");
+        }
+        QueryWrapper<MomentLikes> queryWrapper=new QueryWrapper<>();
+        queryWrapper.eq("moment_id",monmentDto.getMomentId());
+        List<MomentLikes> momentLikes = momentLikesMapper.selectList(queryWrapper);
+//        如果没有点赞
+        if (CollectionUtils.isEmpty(momentLikes)){
+            Map<String,Object> map = new HashMap<>();
+            map.put("likeList", Collections.emptyList());
+            map.put("userList", Collections.emptyList());
+            map.put("total", 0);
+            return Result.OK(map, "查询成功,暂无点赞数据");
+        }
+        // 提取去重后的用户ID列表
+        List<Long> userIds = momentLikes.stream()
+                .map(MomentLikes::getUserId)
+                .distinct()
+                .collect(Collectors.toList());
+        List<Long> userLikes=new ArrayList<>();
+        for (MomentLikes like:momentLikes){
+            userLikes.add(like.getUserId());
+        }
+        // 查询用户信息(需恢复此功能)
+        List<UserLogin> userLogins = userLoginMapper.selectBatchIds(userIds);
+        Map<Long, UserLogin> userMap = userLogins.stream()
+                .collect(Collectors.toMap(UserLogin::getId, user -> user));
+
+        Map<String, Object> map = new HashMap<>();
+        map.put("likeList",momentLikes);
+       map.put("userList",userLogins);
+        return Result.OK(map,"查询成功");
+    }
+    //  查询评论列表
+    @Override
+    public Result commentList(Long valueOf, CommentsDto commentsDto) {
+        QueryWrapper<MomentComments> queryWrapper=new QueryWrapper<>();
+        queryWrapper.eq("moment_id",commentsDto.getMomentId());
+        queryWrapper.orderByDesc("created_at");
+        List<MomentComments> momentComments = momentCommentsMapper.selectList(queryWrapper);
+//        查询朋友圈发布者信息
+        UserMoments userMoments = userMomentsMapper.selectById(commentsDto.getMomentId());
+        Long publisherId = null;
+        if (userMoments!=null){
+            publisherId = userMoments.getUserId();
+        }
+        List<Long> replyToUserIds=new ArrayList<>();  //存储每条评论的replyToUser
+        List<Long> replyToMomentIds=new ArrayList<>();      //存储每条评论的朋友圈id
+        List<String> userUsernames=new ArrayList<>();
+        for (MomentComments comment:momentComments){
+            UserLogin user = userLoginMapper.selectById(comment.getUserId());
+            if (user!=null){
+                userUsernames.add(user.getUserUsername());
+            }
+            // 设置replyTo和replyToUser
+            if (comment.getReplyTo() == null) {
+                // 如果当前评论没有回复目标(直接评论朋友圈)
+                replyToMomentIds.add(comment.getMomentId()); // replyTo设为当前朋友圈ID
+                replyToUserIds.add(publisherId); // replyToUser设为朋友圈发布者ID
+            } else {
+                // 如果当前评论是回复其他评论
+                replyToMomentIds.add(comment.getReplyTo()); // replyTo设为被回复的评论ID
+                replyToUserIds.add(comment.getReplyToUser()); // replyToUser设为被回复的用户ID
+            }
+        }
+        commentsDto.setUserUsernames(userUsernames);
+        Map<String, Object> map = new HashMap<>();
+        map.put("commentList",momentComments);
+        map.put("usernames",userUsernames);
+        map.put("replyToUserIds", replyToUserIds); // 添加replyToUser
+        map.put("replyToMomentIds", replyToMomentIds); // 添加replyTo
+        return Result.OK(map,"查询成功");
+    }
+    //    更改朋友圈背景图
+    @Override
+    public Result updateBackground(Long userId, MultipartFile file) throws IOException {
+        String ossUrl = ossUtil.uploadFile(file);
+        UserLogin userLogin=new UserLogin();
+        userLogin.setId(userId);
+        userLogin.setLabelList(ossUrl);
+        userLoginMapper.updateById(userLogin);
+        return Result.OK(ossUrl,"修改成功");
+    }
+
+
+    @Override
+    public Result allListPage(Long userId, MonmentDto monmentDto) {
+        try {
+            // 1. 校验用户ID
+            if (userId == null) {
+                return Result.error(400, "用户ID不能为空");
+            }
+
+            // 2. 查询个人信息
+            UserLogin userLogin = userLoginMapper.selectById(userId);
+            if (userLogin == null) {
+                return Result.error(404, "用户信息不存在");
+            }
+
+            // 3. 查询好友关系
+            QueryWrapper<UserRelationships> friendRelQuery = new QueryWrapper<>();
+            friendRelQuery.eq("user_id", userId)
+                    .eq("status", 1)
+                    .eq("is_blacklist", 0)
+                    .eq("is_del", 0)
+                    .eq("is_moments", 0)
+                    .orderByDesc("created_at");
+            List<UserRelationships> friendRelationships = userRelationshipsMapper.selectList(friendRelQuery);
+            List<Long> friendIds = friendRelationships.stream()
+                    .map(UserRelationships::getFriendId)
+                    .collect(Collectors.toList());
+
+            // 4. 合并查询当前用户和好友的动态
+            Page<UserMoments> allMomentsPage = new Page<>(monmentDto.getCurrentPage(), monmentDto.getPageSize());
+            QueryWrapper<UserMoments> allMomentQuery = new QueryWrapper<>();
+
+            // 如果有好友,查询自己和好友的动态;否则只查询自己的
+            if (!friendIds.isEmpty()) {
+                allMomentQuery.in("user_id", Stream.concat(
+                        Stream.of(userId),
+                        friendIds.stream()
+                ).collect(Collectors.toList()));
+            } else {
+                allMomentQuery.eq("user_id", userId);
+            }
+
+            // 排除仅自己可见的动态(除了自己发布的)
+            allMomentQuery.and(wrapper ->
+                    wrapper.eq("user_id", userId)
+                            .or()
+                            .ne("visibility", 2)
+            );
+
+            // 按创建时间降序排列
+            allMomentQuery.orderByDesc("created_at");
+
+            // 执行分页查询
+            userMomentsMapper.selectPage(allMomentsPage, allMomentQuery);
+            List<UserMoments> allUserMoments = allMomentsPage.getRecords();
+
+            // 5. 提取所有动态相关用户ID(原有代码)
+            Set<Long> allUserIds = allUserMoments.stream()
+                    .map(UserMoments::getUserId)
+                    .collect(Collectors.toSet());
+
+            // 6. 批量查询所有动态相关用户信息(原有代码)
+            QueryWrapper<UserLogin> userQuery = new QueryWrapper<>();
+            userQuery.in("id", allUserIds);
+            List<UserLogin> users = userLoginMapper.selectList(userQuery);
+            Map<Long, UserLogin> userMap = users.stream()
+                    .collect(Collectors.toMap(UserLogin::getId, user -> user));
+
+            // 7. 提取所有动态ID
+            List<Long> allMomentIds = allUserMoments.stream()
+                    .map(UserMoments::getMomentId)
+                    .collect(Collectors.toList());
+
+            // 8. 批量查询当前用户对所有动态的点赞状态(性能优化点)
+            Map<Long, Boolean> likedStatusMap = new HashMap<>();
+            if (!allMomentIds.isEmpty()) {
+                QueryWrapper<MomentLikes> batchLikeQuery = new QueryWrapper<>();
+                batchLikeQuery.in("moment_id", allMomentIds)
+                        .eq("user_id", userId);
+                List<MomentLikes> likedMoments = momentLikesMapper.selectList(batchLikeQuery);
+
+                // 将结果转换为 Map<momentId, isLiked>
+                for (Long momentId : allMomentIds) {
+                    likedStatusMap.put(momentId, likedMoments.stream()
+                            .anyMatch(like -> like.getMomentId().equals(momentId)));
+                }
+            }
+
+            // 9. 处理动态数据,添加用户信息和点赞状态(优化后)
+            List<Map<String, Object>> momentList = allUserMoments.stream()
+                    .map(moment -> {
+                        Map<String, Object> momentMap = wrapMomentWithUser(moment, userMap);
+                        // 从批量查询结果中获取点赞状态,避免重复查询
+                        momentMap.put("isLiked", likedStatusMap.getOrDefault(moment.getMomentId(), false));
+                        return momentMap;
+                    })
+                    .collect(Collectors.toList());
+
+            // 10. 处理点赞和评论(原有代码,略作优化)
+            Map<Long, Object> allLikesAndComments = new HashMap<>();
+            if (!allMomentIds.isEmpty()) {
+                // 批量查询所有动态的点赞
+                Map<Long, List<MomentLikes>> likesMap = new HashMap<>();
+                QueryWrapper<MomentLikes> likeQuery = new QueryWrapper<>();
+                likeQuery.in("moment_id", allMomentIds);
+                List<MomentLikes> allLikes = momentLikesMapper.selectList(likeQuery);
+                for (MomentLikes like : allLikes) {
+                    likesMap.computeIfAbsent(like.getMomentId(), k -> new ArrayList<>()).add(like);
+                }
+
+                // 批量查询所有动态的评论
+                Map<Long, List<MomentComments>> commentsMap = new HashMap<>();
+                QueryWrapper<MomentComments> commentQuery = new QueryWrapper<>();
+                commentQuery.in("moment_id", allMomentIds)
+                        .orderByDesc("created_at");
+                List<MomentComments> allComments = momentCommentsMapper.selectList(commentQuery);
+                for (MomentComments comment : allComments) {
+                    commentsMap.computeIfAbsent(comment.getMomentId(), k -> new ArrayList<>()).add(comment);
+                }
+
+                // 提取所有点赞和评论涉及的用户ID
+                Set<Long> likeCommentUserIds = new HashSet<>();
+                allLikes.forEach(like -> likeCommentUserIds.add(like.getUserId()));
+                allComments.forEach(comment -> {
+                    likeCommentUserIds.add(comment.getUserId());
+                    if (comment.getReplyToUser() != null) {
+                        likeCommentUserIds.add(comment.getReplyToUser());
+                    }
+                });
+
+                // 批量查询用户信息
+                List<UserLogin> likeCommentUsers = new ArrayList<>();
+                if (!likeCommentUserIds.isEmpty()) {
+                    likeCommentUsers = userLoginMapper.selectBatchIds(likeCommentUserIds);
+                }
+                Map<Long, UserLogin> likeCommentUserMap = likeCommentUsers.stream()
+                        .collect(Collectors.toMap(UserLogin::getId, user -> user));
+
+                // 处理每个动态的点赞和评论信息
+                for (Long momentId : allMomentIds) {
+                    Map<String, Object> momentInfo = new HashMap<>();
+
+                    // 获取当前动态的点赞列表
+                    List<MomentLikes> likes = likesMap.getOrDefault(momentId, Collections.emptyList());
+                    List<Map<String, Object>> likeList = likes.stream()
+                            .map(like -> {
+                                Map<String, Object> likeInfo = new HashMap<>();
+                                likeInfo.put("id", like.getLikeId());
+                                likeInfo.put("userId", like.getUserId());
+
+                                if (like.getCreatedAt() != null) {
+                                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                                    likeInfo.put("createTime", sdf.format(like.getCreatedAt()));
+                                }
+
+                                // 手动构建用户信息
+                                UserLogin user = likeCommentUserMap.get(like.getUserId());
+                                if (user != null) {
+                                    Map<String, Object> userInfo = new HashMap<>();
+                                    userInfo.put("id", user.getId().toString());
+                                    userInfo.put("nickName", user.getNickName());
+                                    userInfo.put("avatar", user.getAvatar());
+                                    userInfo.put("username", user.getUserUsername());
+                                    likeInfo.put("userInfo", userInfo);
+                                }
+                                return likeInfo;
+                            })
+                            .collect(Collectors.toList());
+
+                    // 获取当前动态的评论列表
+                    List<MomentComments> comments = commentsMap.getOrDefault(momentId, Collections.emptyList());
+                    List<Map<String, Object>> commentList = comments.stream()
+                            .map(comment -> {
+                                Map<String, Object> commentInfo = new HashMap<>();
+                                commentInfo.put("id", comment.getCommentId());
+                                commentInfo.put("userId", comment.getUserId());
+                                commentInfo.put("content", comment.getContent());
+
+                                if (comment.getCreatedAt() != null) {
+                                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                                    commentInfo.put("createdAt", sdf.format(comment.getCreatedAt()));
+                                }
+
+                                commentInfo.put("replyTo", comment.getReplyTo());
+                                commentInfo.put("replyToUser", comment.getReplyToUser());
+
+                                UserLogin user = likeCommentUserMap.get(comment.getUserId());
+                                if (user != null) {
+                                    Map<String, Object> userInfo = new HashMap<>();
+                                    userInfo.put("id", user.getId().toString());
+                                    userInfo.put("username", user.getUserUsername());
+                                    userInfo.put("avatar", user.getAvatar());
+                                    commentInfo.put("userInfo", userInfo);
+                                }
+                                return commentInfo;
+                            })
+                            .collect(Collectors.toList());
+
+                    // 封装点赞评论信息
+                    momentInfo.put("likeList", likeList);
+                    momentInfo.put("commentList", commentList);
+                    momentInfo.put("likeCount", likeList.size());
+                    momentInfo.put("commentCount", commentList.size());
+
+                    allLikesAndComments.put(momentId, momentInfo);
+                }
+            }
+
+            // 11. 封装最终结果(原有代码)
+            Map<String, Object> resultMap = new HashMap<>();
+            resultMap.put("userInfo", userLogin);
+            resultMap.put("moments", momentList);
+            resultMap.put("allLikesAndComments", allLikesAndComments);
+            resultMap.put("total", allMomentsPage.getTotal());
+            resultMap.put("currentPage", allMomentsPage.getCurrent());
+            resultMap.put("pageSize", allMomentsPage.getSize());
+            resultMap.put("totalPages", allMomentsPage.getPages());
+
+            return Result.OK(resultMap, "查询成功");
+        } catch (Exception e) {
+            log.error("综合朋友圈查询异常: {}", e.getMessage(), e);
+            return Result.error(500, "查询失败");
+        }
+    }
+
+    /**
+     * 封装动态数据并关联用户信息,确保包含momentId
+     */
+    private Map<String, Object> wrapMomentWithUser(UserMoments moment, Map<Long, UserLogin> userMap) {
+        Map<String, Object> momentMap = new HashMap<>();
+
+        // 手动添加所有需要的字段,确保momentId存在
+        momentMap.put("momentId", moment.getMomentId().toString());
+        momentMap.put("contentType", moment.getContentType());
+        momentMap.put("url", moment.getUrl());
+        momentMap.put("content", moment.getContent());
+        momentMap.put("location", moment.getLocation());
+        momentMap.put("visibility", moment.getVisibility());
+
+        // 转换日期为字符串格式
+        if (moment.getCreatedAt() != null) {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            momentMap.put("createdAt", sdf.format(moment.getCreatedAt()));
+        }
+
+        // 手动添加用户信息,只包含必要字段
+        UserLogin user = userMap.get(moment.getUserId());
+        if (user != null) {
+            Map<String, Object> userInfo = new HashMap<>();
+            userInfo.put("id", user.getId());
+            userInfo.put("nickName", user.getNickName());
+            userInfo.put("avatar", user.getAvatar());
+            userInfo.put("username", user.getUserUsername());
+            momentMap.put("userInfo", userInfo);
+        }
+
+        momentMap.put("isSelf", user != null && user.getId().equals(moment.getUserId()));
+        return momentMap;
+    }
+    /**
+    * 上传到oss
+    * */
+    private String uploadToOss(MultipartFile file, String ossFilePath) throws IOException {
+        OSSClient ossClient = new OSSClient(ossConfig.getEndpoint(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret());
+        String bucketName = ossConfig.getBucketName();
+
+        // 创建PutObject请求
+        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, ossFilePath,
+                file.getInputStream(), null);
+
+        // 设置对象元信息
+        ObjectMetadata metadata = new ObjectMetadata();
+        metadata.setContentType(file.getContentType());
+        metadata.setContentLength(file.getSize());
+        putObjectRequest.setMetadata(metadata);
+
+        // 上传文件
+        ossClient.putObject(putObjectRequest);
+
+        // 构建访问URL
+        String endpoint = ossConfig.getEndpoint();
+        String cleanEndpoint = endpoint.replaceAll("^https://", "");
+        String domain = "https://" + bucketName + "." + cleanEndpoint;
+        return domain + "/" + ossFilePath;
+    }
+
+    /**
+     * 获取文件扩展名
+     */
+    private String getFileExtension(String fileName) {
+        if (fileName == null || !fileName.contains(".")) {
+            return "";
+        }
+        return fileName.substring(fileName.lastIndexOf("."));
+    }
+
+    /**
+     * 验证图片内容
+     */
+    private void validateImageContent(MultipartFile file) throws Exception {
+        if (file == null || file.isEmpty()) {
+            throw new Exception("图片不能为空");
+        }
+
+        String contentType = file.getContentType();
+        if (!contentType.startsWith("image/")) {
+            throw new Exception("请上传图片格式文件");
+        }
+
+        // 验证图片大小(例如限制为10MB)
+        if (file.getSize() > ossConfig.getUpload().getImage().getMaxSize()) {
+            throw new Exception("图片大小不能超过" +
+                    (ossConfig.getUpload().getImage().getMaxSize() / (1024 * 1024)) + "MB");
+        }
+    }
+
+    /**
+     * 验证视频内容
+     */
+    private void validateVideoContent(MultipartFile file) throws Exception {
+        if (file == null || file.isEmpty()) {
+            throw new Exception("视频不能为空");
+        }
+
+        String contentType = file.getContentType();
+        if (!contentType.startsWith("video/")) {
+            throw new Exception("请上传视频格式文件");
+        }
+
+        // 验证视频大小(例如限制为50MB)
+        if (file.getSize() > ossConfig.getUpload().getVideo().getMaxSize()) {
+            throw new Exception("视频大小不能超过" +
+                    (ossConfig.getUpload().getVideo().getMaxSize() / (1024 * 1024)) + "MB");
+        }
+
+    }
+}
+
+
+
+

+ 77 - 0
src/main/java/com/zhentao/osspicture/OssConfig.java

@@ -0,0 +1,77 @@
+package com.zhentao.osspicture;
+
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "aliyun.oss")
+public class OssConfig {
+    @Value("${aliyun.oss.endpoint}")
+    private String endpoint;
+    @Value("${aliyun.oss.accessKeyId}")
+    private String accessKeyId;
+    @Value("${aliyun.oss.accessKeySecret}")
+    private String accessKeySecret;
+    @Value("${aliyun.oss.bucketName}")
+    private String bucketName;
+    private String fileHost;
+    private Integer urlExpireTime;
+    /**
+     //     * 上传配置
+     //     */
+    private Upload upload = new Upload();
+
+    /**
+     * 上传配置内部类
+     */
+    @Data
+    public static class Upload {
+
+        /**
+         * 图片上传配置
+         */
+        private Image image = new Image();
+
+        /**
+         * 视频上传配置
+         */
+        private Video video = new Video();
+
+        /**
+         * 图片上传配置内部类
+         */
+        @Data
+        public static class Image {
+
+            /**
+             * 图片上传目录
+             */
+            private String dir = "moments/images/";
+
+            /**
+             * 图片最大大小(字节),默认10MB
+             */
+            private long maxSize = 10 * 1024 * 1024;
+        }
+
+        /**
+         * 视频上传配置内部类
+         */
+        @Data
+        public static class Video {
+
+            /**
+             * 视频上传目录
+             */
+            private String dir = "moments/videos/";
+
+            /**
+             * 视频最大大小(字节),默认50MB
+             */
+            private long maxSize = 50 * 1024 * 1024;
+        }
+    }
+}

+ 231 - 0
src/main/java/com/zhentao/osspicture/OssUtil.java

@@ -0,0 +1,231 @@
+package com.zhentao.osspicture;
+
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import com.aliyun.oss.model.ObjectMetadata;
+import com.aliyun.oss.model.PutObjectRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+@Component
+@Slf4j
+public class OssUtil {
+
+    // 从配置文件中读取OSS相关信息
+    private static final String endpoint = "https://oss-cn-beijing.aliyuncs.com";
+    private static final String accessKeyId = "LTAI5tH9VHPZwGJu4UX3hrL5";
+    private static final String accessKeySecret = "mbsutFJYLkzosvvKNr0DD28XSg4mqA";
+    private static final String bucketName = "fjj1";
+    private static final boolean useCDN =  true;
+
+    private static final String cdnDomain = "https://cdn.yourdomain.com";
+
+    // MIME类型到扩展名的映射
+    private static final Map<String, String> MIME_TO_EXTENSION = new HashMap<>();
+    static {
+        // 图片类型
+        MIME_TO_EXTENSION.put("image/jpeg", "jpg");
+        MIME_TO_EXTENSION.put("image/png", "png");
+        MIME_TO_EXTENSION.put("image/gif", "gif");
+        MIME_TO_EXTENSION.put("image/bmp", "bmp");
+        MIME_TO_EXTENSION.put("image/webp", "webp");
+        MIME_TO_EXTENSION.put("image/svg+xml", "svg");
+
+        // 视频类型
+        MIME_TO_EXTENSION.put("video/mp4", "mp4");
+        MIME_TO_EXTENSION.put("video/quicktime", "mov");
+        MIME_TO_EXTENSION.put("video/x-msvideo", "avi");
+        MIME_TO_EXTENSION.put("video/x-matroska", "mkv");
+        MIME_TO_EXTENSION.put("video/x-flv", "flv");
+        MIME_TO_EXTENSION.put("video/webm", "webm");
+
+        // 音频类型
+        MIME_TO_EXTENSION.put("audio/mpeg", "mp3");
+        MIME_TO_EXTENSION.put("audio/wav", "wav");
+        MIME_TO_EXTENSION.put("audio/ogg", "ogg");
+        MIME_TO_EXTENSION.put("audio/flac", "flac");
+        MIME_TO_EXTENSION.put("audio/aac", "aac");
+
+        // 文档类型
+        MIME_TO_EXTENSION.put("application/pdf", "pdf");
+        MIME_TO_EXTENSION.put("application/msword", "doc");
+        MIME_TO_EXTENSION.put("application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx");
+        MIME_TO_EXTENSION.put("application/vnd.ms-excel", "xls");
+        MIME_TO_EXTENSION.put("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx");
+        MIME_TO_EXTENSION.put("application/vnd.ms-powerpoint", "ppt");
+        MIME_TO_EXTENSION.put("application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx");
+        MIME_TO_EXTENSION.put("text/plain", "txt");
+    }
+
+
+    public String uploadFile(MultipartFile file) throws IOException {
+        if (file == null || file.isEmpty()) {
+            throw new IllegalArgumentException("上传文件不能为空");
+        }
+
+        OSS ossClient = null;
+        try {
+            ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
+
+            String originalFilename = file.getOriginalFilename();
+            String contentType = file.getContentType();
+            long fileSize = file.getSize();
+
+            // 确定文件类型和扩展名
+            FileTypeInfo fileTypeInfo = determineFileType(originalFilename, contentType);
+            String extension = fileTypeInfo.getExtension();
+            String fileCategory = fileTypeInfo.getCategory();
+
+            // 生成唯一的文件名
+            String fileName = generateUniqueFileName(fileCategory, extension);
+
+            // 创建文件元数据
+            ObjectMetadata metadata = createMetadata(originalFilename, contentType, fileSize, fileCategory);
+
+            // 上传文件
+            try (InputStream inputStream = file.getInputStream()) {
+                PutObjectRequest putObjectRequest = new PutObjectRequest(
+                        bucketName,
+                        fileName,
+                        inputStream,
+                        metadata
+                );
+                ossClient.putObject(putObjectRequest);
+            }
+
+            // 生成文件访问URL
+            String fileUrl = generateFileUrl(fileName);
+            log.info("文件上传成功: {} ({}), 大小: {} KB, URL: {}",
+                    originalFilename, fileCategory, fileSize / 1024, fileUrl);
+
+            return fileUrl;
+        } catch (Exception e) {
+            log.error("文件上传失败: {}", e.getMessage(), e);
+            throw new IOException("文件上传失败: " + e.getMessage(), e);
+        } finally {
+            if (ossClient != null) {
+                ossClient.shutdown();
+            }
+        }
+    }
+
+    private String generateUniqueFileName(String category, String extension) {
+        return "uploads/" + category + "/" + UUID.randomUUID() + "." + extension;
+    }
+
+    private ObjectMetadata createMetadata(String filename, String contentType,
+                                          long fileSize, String category) {
+        ObjectMetadata metadata = new ObjectMetadata();
+
+        // 设置基础元数据
+        metadata.setContentType(contentType != null ? contentType : "application/octet-stream");
+        metadata.setContentLength(fileSize);
+
+        // 设置下载时的文件名
+        if (filename != null) {
+            metadata.setContentDisposition("attachment; filename=\"" + filename + "\"");
+        }
+
+        // 添加自定义元数据
+        metadata.addUserMetadata("Original-Filename", filename != null ? filename : "");
+        metadata.addUserMetadata("File-Category", category);
+
+        return metadata;
+    }
+
+    private FileTypeInfo determineFileType(String filename, String contentType) {
+        String extension = "bin";
+        String category = "other";
+
+        // 1. 尝试从文件名获取扩展名
+        if (filename != null && filename.contains(".")) {
+            String fileExt = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
+            if (isValidExtension(fileExt)) {
+                extension = fileExt;
+                category = determineCategory(extension);
+            }
+        }
+
+        // 2. 如果扩展名无效,尝试从内容类型获取
+        if ("bin".equals(extension) && contentType != null) {
+            // 使用映射表获取标准扩展名
+            String mappedExt = MIME_TO_EXTENSION.get(contentType.toLowerCase());
+            if (mappedExt != null) {
+                extension = mappedExt;
+                category = determineCategory(extension);
+            }
+        }
+
+        // 3. 最终确定文件分类
+        if ("other".equals(category)) {
+            category = determineCategory(extension);
+        }
+
+        return new FileTypeInfo(extension, category);
+    }
+
+    private boolean isValidExtension(String ext) {
+        // 检查扩展名是否有效(不含特殊字符)
+        return ext.matches("[a-z0-9]{1,10}");
+    }
+
+    private String determineCategory(String extension) {
+        if (extension.matches("png|jpe?g|gif|bmp|webp|svg|heic|heif")) {
+            return "images";
+        } else if (extension.matches("mp4|mov|avi|mkv|flv|webm|m4v|wmv|3gp")) {
+            return "videos";
+        } else if (extension.matches("mp3|wav|ogg|flac|aac|m4a|wma|amr")) {
+            return "audios";
+        } else if (extension.matches("pdf|docx?|xlsx?|pptx?|txt|rtf|csv|pages|numbers|key")) {
+            return "documents";
+        } else if (extension.matches("zip|rar|7z|tar|gz")) {
+            return "archives";
+        }
+        return "other";
+    }
+
+    private String generateFileUrl(String fileName) {
+        // 使用CDN加速域名(如果配置了),否则使用标准OSS域名
+        if (useCDN && cdnDomain != null && !cdnDomain.isEmpty()) {
+            return cdnDomain + "/" + fileName;
+        }
+
+        // 构建标准OSS域名
+        String domain = endpoint.startsWith("http") ?
+                endpoint : "https://" + endpoint;
+
+        return domain.replaceFirst("://", "://" + bucketName + ".") + "/" + fileName;
+    }
+
+    // 文件类型信息辅助类
+    private static class FileTypeInfo {
+        private final String extension;
+        private final String category;
+
+        public FileTypeInfo(String extension, String category) {
+            this.extension = extension;
+            this.category = category;
+        }
+
+        public String getExtension() {
+            return extension;
+        }
+
+        public String getCategory() {
+            return category;
+        }
+    }
+
+
+}

+ 110 - 0
src/main/java/com/zhentao/osspicture/ossController.java

@@ -0,0 +1,110 @@
+package com.zhentao.osspicture;
+
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import com.aliyun.oss.model.PutObjectRequest;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+@RestController
+@RequestMapping("/api/upload")
+public class ossController {
+
+    @Value("${aliyun.oss.endpoint}")
+    private String endpoint;
+
+    @Value("${aliyun.oss.accessKeyId}")
+    private String accessKeyId;
+
+    @Value("${aliyun.oss.accessKeySecret}")
+    private String accessKeySecret;
+
+    @Value("${aliyun.oss.bucketName}")
+    private String bucketName;
+
+    // 允许的图片格式
+    private static final String[] ALLOWED_TYPES = {
+            "image/jpeg", "image/png", "image/gif"
+    };
+
+    @PostMapping("/simple-image")
+    public Map<String, Object> uploadSimpleImage(@RequestParam("file") MultipartFile file) {
+        Map<String, Object> result = new HashMap<>();
+
+        // 基础验证
+        if (file == null || file.isEmpty()) {
+            result.put("code", 400);
+            result.put("message", "上传图片不能为空");
+            return result;
+        }
+
+        // 验证图片格式
+        String contentType = file.getContentType();
+        if (!isValidImageType(contentType)) {
+            result.put("code", 400);
+            result.put("message", "仅支持JPG、PNG、GIF格式的图片");
+            return result;
+        }
+
+        OSS ossClient = null;
+        try {
+            ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
+
+            // 生成唯一文件名
+            String originalFilename = file.getOriginalFilename();
+            String extension = getFileExtension(originalFilename);
+            String fileName = UUID.randomUUID().toString() + "." + extension;
+
+            // 上传图片
+            PutObjectRequest putObjectRequest = new PutObjectRequest(
+                    bucketName, fileName, file.getInputStream()
+            );
+            ossClient.putObject(putObjectRequest);
+
+            // 构建访问URL
+            String endpointWithoutProtocol = endpoint.replaceFirst("^https?://", "");
+            String imageUrl = "https://" + bucketName + "." + endpointWithoutProtocol + "/" + fileName;
+
+            // 返回结果
+            result.put("code", 200);
+            result.put("message", "图片上传成功");
+            result.put("imageUrl", imageUrl);
+
+        } catch (IOException e) {
+            result.put("code", 500);
+            result.put("message", "上传失败:" + e.getMessage());
+        } finally {
+            if (ossClient != null) {
+                ossClient.shutdown();
+            }
+        }
+
+        return result;
+    }
+
+    private boolean isValidImageType(String contentType) {
+        if (contentType == null) return false;
+        for (String type : ALLOWED_TYPES) {
+            if (contentType.equalsIgnoreCase(type)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private String getFileExtension(String fileName) {
+        if (fileName == null || !fileName.contains(".")) {
+            return "jpg";
+        }
+        return fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
+    }
+}

+ 34 - 0
src/main/java/com/zhentao/shouye/controller/UserShouyeController.java

@@ -0,0 +1,34 @@
+package com.zhentao.shouye.controller;
+
+import com.zhentao.config.NullLogin;
+import com.zhentao.shouye.dto.UserShouyeDto;
+import com.zhentao.shouye.service.UserShouyeService;
+import com.zhentao.tool.TokenUtils;
+import com.zhentao.vo.Result;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("shouye")
+public class UserShouyeController {
+    @Autowired
+    private UserShouyeService userShouyeService;
+
+    @GetMapping("findAll")
+    @NullLogin
+    public Result findAll(@RequestHeader("token") String token){
+        System.err.println(token);
+        String userIdFromToken = TokenUtils.getUserIdFromToken(token);
+        UserShouyeDto userShouyeDto = new UserShouyeDto();
+        userShouyeDto.setUid1(Long.valueOf(userIdFromToken));
+        System.err.println(userShouyeDto);
+        return userShouyeService.findAll(userShouyeDto);
+    }
+
+    @PostMapping("add")
+    @NullLogin
+    public Result add(@RequestHeader("token") String token,@RequestBody UserShouyeDto userShouyeDto){
+        String userIdFromToken = TokenUtils.getUserIdFromToken(token);
+        return userShouyeService.add(userIdFromToken,userShouyeDto);
+    }
+}

+ 72 - 0
src/main/java/com/zhentao/shouye/domain/Groups.java

@@ -0,0 +1,72 @@
+package com.zhentao.shouye.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 群组表
+ * @TableName groups
+ */
+@TableName(value ="groupss")
+@Data
+public class Groups implements Serializable {
+    /**
+     * id
+
+     */
+    @TableId(type = IdType.AUTO)
+    private Long groupId;
+
+    /**
+     * 群名称
+     */
+    private String name;
+
+    /**
+     * 创建者ID
+     */
+    private Long creatorId;
+
+    /**
+     * 群头像
+     */
+    private String avatar;
+
+    /**
+     * 群公告
+     */
+    private String announcement;
+
+    /**
+     * 群描述
+     */
+    private String description;
+
+    /**
+     * 最大成员数
+     */
+    private Integer maxMembers;
+
+    /**
+     * 状态(0-解散,1-正常)
+     */
+    private Integer status;
+
+    /**
+     * 创建时间
+     */
+    private Date createdAt;
+
+    /**
+     * 修改时间
+     */
+    private Date updatedAt;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+}

+ 55 - 0
src/main/java/com/zhentao/shouye/domain/UserShouye.java

@@ -0,0 +1,55 @@
+package com.zhentao.shouye.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+
+import com.zhentao.groups.pojo.Groupss;
+import com.zhentao.user.domain.UserLogin;
+import lombok.Data;
+
+/**
+ * 用户首页表
+ * @TableName user_shouye
+ */
+@TableName(value ="user_shouye")
+@Data
+public class UserShouye implements Serializable {
+    /**
+     * 主键
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 用户1
+     */
+    private Long uid1;
+
+    /**
+     * 用户2
+     */
+    private Long uid2;
+
+    /**
+     * 群id
+     */
+    private Long gid;
+
+    /**
+     * 状态
+     */
+    private Integer status;
+
+    /**
+     * 排序
+     */
+    private Integer sort;
+    @TableField(exist = false)
+    private UserLogin userLogin;
+    @TableField(exist = false)
+    private Groupss groupss;
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+}

+ 9 - 0
src/main/java/com/zhentao/shouye/dto/UserShouyeDto.java

@@ -0,0 +1,9 @@
+package com.zhentao.shouye.dto;
+
+import lombok.Data;
+
+@Data
+public class UserShouyeDto {
+//    用户的ID
+    private Long uid1;
+}

+ 20 - 0
src/main/java/com/zhentao/shouye/mapper/UserShouyeMapper.java

@@ -0,0 +1,20 @@
+package com.zhentao.shouye.mapper;
+
+import com.zhentao.shouye.domain.UserShouye;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+* @author 86183
+* @description 针对表【user_shouye(用户首页表)】的数据库操作Mapper
+* @createDate 2025-06-04 19:44:02
+* @Entity com.zhentao.shouye.domain.UserShouye
+*/
+@Mapper
+public interface UserShouyeMapper extends BaseMapper<UserShouye> {
+
+}
+
+
+
+

+ 16 - 0
src/main/java/com/zhentao/shouye/service/UserShouyeService.java

@@ -0,0 +1,16 @@
+package com.zhentao.shouye.service;
+
+import com.zhentao.shouye.domain.UserShouye;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhentao.shouye.dto.UserShouyeDto;
+import com.zhentao.vo.Result;
+
+/**
+* @author 86183
+* @description 针对表【user_shouye(用户首页表)】的数据库操作Service
+* @createDate 2025-06-04 19:44:02
+*/
+public interface UserShouyeService extends IService<UserShouye> {
+    Result findAll(UserShouyeDto dto);
+    Result add(String uid1,UserShouyeDto dto);
+}

+ 95 - 0
src/main/java/com/zhentao/shouye/service/impl/UserShouyeServiceImpl.java

@@ -0,0 +1,95 @@
+package com.zhentao.shouye.service.impl;
+
+import cn.hutool.core.util.IdUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.groups.mapper.GroupsMapper;
+import com.zhentao.groups.pojo.Groupss;
+import com.zhentao.shouye.domain.UserShouye;
+import com.zhentao.shouye.dto.UserShouyeDto;
+import com.zhentao.shouye.service.UserShouyeService;
+import com.zhentao.shouye.mapper.UserShouyeMapper;
+import com.zhentao.user.domain.UserLogin;
+import com.zhentao.user.service.UserLoginService;
+import com.zhentao.vo.Result;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+* @author 86183
+* @description 针对表【user_shouye(用户首页表)】的数据库操作Service实现
+* @createDate 2025-06-04 19:44:02
+*/
+@Service
+public class UserShouyeServiceImpl extends ServiceImpl<UserShouyeMapper, UserShouye>
+    implements UserShouyeService{
+    @Autowired
+    private UserLoginService userLoginService;
+    @Autowired
+    private GroupsMapper groupsService;
+    public static boolean isLongNotNull(long value) {
+        // 如果值为0L,认为是“空”,返回false
+        return value != 0;
+    }
+    @Override
+    public Result findAll(UserShouyeDto dto) {
+        System.err.println(dto.getUid1());
+//        将所有正在联系的用户展示
+        QueryWrapper<UserShouye> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("uid1",  dto.getUid1());
+        queryWrapper.eq("status",0);
+        List<UserShouye> list = this.list(queryWrapper);
+//        System.err.println(list);
+//        关联用户  群关联
+//        查出所有的用户信息
+        List<UserLogin> list1 = userLoginService.list();
+        List<Groupss> list2 = groupsService.selectList(null);
+//        进行关联
+        for (UserShouye u:list) {
+            for (UserLogin l: list1) {
+                if (u.getUid2().equals(l.getId()) && isLongNotNull(u.getUid2())){
+                    u.setUserLogin(l);
+                }
+            }
+        }
+        System.err.println(list);
+        for (UserShouye u:list) {
+            for (Groupss g: list2) {
+                if (u.getGid().equals(g.getGroupId()) && isLongNotNull(u.getGid())){
+                    u.setGroupss(g);
+                }
+            }
+        }
+        System.err.println(list);
+        return Result.OK(list,  "查询成功");
+    }
+
+    @Override
+    public Result add(String uid1,UserShouyeDto dto) {
+
+        QueryWrapper<UserShouye> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("uid1", uid1);
+        queryWrapper.eq("uid2", dto.getUid1());
+        UserShouye one = this.getOne(queryWrapper);
+        if (one==null){
+            UserShouye userShouye = new UserShouye();
+            long l = IdUtil.getSnowflake(1, 1).nextId();
+            userShouye.setId(l);
+            userShouye.setUid1(Long.valueOf(uid1));
+            userShouye.setUid2(dto.getUid1());
+            userShouye.setStatus(0);
+            this.save(userShouye);
+            return Result.OK(null, "添加成功");
+        }else {
+            return Result.OK(null, "已存在");
+        }
+
+
+    }
+}
+
+
+
+

+ 94 - 0
src/main/java/com/zhentao/tool/TokenUtils.java

@@ -0,0 +1,94 @@
+package com.zhentao.tool;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+public class TokenUtils {
+
+    // 密钥,生产环境应该从配置文件中读取
+    private static final String SECRET_KEY = "your-secret-key-here";
+
+    // Token有效期(毫秒),这里设置为24小时
+    private static final long EXPIRATION_TIME = 24 * 60 * 60 * 1000;
+
+    /**
+     * 生成JWT Token
+     * @param userId 用户ID
+     * @return JWT Token
+     */
+    public static String generateToken(String userId) {
+        Map<String, Object> claims = new HashMap<>();
+        claims.put("userId", userId);
+
+        return Jwts.builder()
+                .setClaims(claims)
+                .setSubject(userId)
+                .setIssuedAt(new Date(System.currentTimeMillis()))
+                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 修正此处
+                .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
+                .compact();
+    }
+    /**
+     * 从Token中获取用户ID
+     * @param token JWT Token
+     * @return 用户ID
+     */
+    public static String getUserIdFromToken(String token) {
+        try {
+            Claims claims = Jwts.parser()
+                    .setSigningKey(SECRET_KEY)
+                    .parseClaimsJws(token)
+                    .getBody();
+
+            return claims.get("userId", String.class);
+        } catch (Exception e) {
+            // 处理Token解析异常
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 验证Token是否有效
+     * @param token JWT Token
+     * @return 是否有效
+     */
+    public static boolean validateToken(String token) {
+        try {
+            Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token);
+            return true;
+        } catch (Exception e) {
+            // 处理Token验证异常
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 从Token中获取所有Claims
+     * @param token JWT Token
+     * @return Claims
+     */
+    private static Claims getAllClaimsFromToken(String token) {
+        return Jwts.parser()
+                .setSigningKey(SECRET_KEY)
+                .parseClaimsJws(token)
+                .getBody();
+    }
+
+    /**
+     * 检查Token是否已过期
+     * @param token JWT Token
+     * @return 是否过期
+     */
+    private static boolean isTokenExpired(String token) {
+        Date expiration = getAllClaimsFromToken(token).getExpiration();
+        return expiration.before(new Date());
+    }
+
+}

+ 33 - 0
src/main/java/com/zhentao/user/controller/LocationController.java

@@ -0,0 +1,33 @@
+package com.zhentao.user.controller;
+
+import com.zhentao.config.NullLogin;
+import com.zhentao.user.dto.LocationDto;
+import com.zhentao.user.service.UserLocationService;
+import com.zhentao.user.service.UserOnlineStatusService;
+import com.zhentao.vo.Result;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+
+@RestController
+@RequestMapping("/location")
+public class LocationController {
+    @Autowired
+    public UserLocationService userLocationService;
+    @Autowired
+    private UserOnlineStatusService onlineStatusService;
+
+    @PostMapping("/update")
+    public Result updateLocation(@RequestBody LocationDto locationDto, @RequestHeader("token") String token) {
+        return userLocationService.updateUserLocation(locationDto,token);
+    }
+    @PostMapping("/getNearbyUsers")
+    public Result getNearbyUsers(@RequestBody LocationDto locationDto, @RequestHeader("token") String token) {
+        return userLocationService.getNearbyUsers(locationDto,token);
+    }
+    @PostMapping("/heartbeat")
+    public void heartbeat(HttpServletRequest request) {
+        onlineStatusService.handleHeartbeat(request);
+    }
+}

+ 60 - 0
src/main/java/com/zhentao/user/controller/QrCodeController.java

@@ -0,0 +1,60 @@
+package com.zhentao.user.controller;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.client.j2se.MatrixToImageWriter;
+import com.google.zxing.common.BitMatrix;
+import com.zhentao.config.NullLogin;
+import com.zhentao.user.dto.UserInfo;
+import org.springframework.web.bind.annotation.*;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+@RequestMapping("api")
+public class QrCodeController {
+
+    // 生成带用户数据的二维码接口
+    @PostMapping("/generate")
+    @NullLogin
+    public Map<String, Object> generateQrCode(@RequestBody UserInfo userInfo) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            // 1. 处理用户数据
+
+            String dataToEncode = userInfo.getUserId().toString();
+
+            // 2. 生成二维码图片
+            int width = 300;
+            int height = 300;
+//            使用ZXIng ("Zebra Crossing")库来生成一个二维码图像
+            BitMatrix bitMatrix = new MultiFormatWriter().encode(dataToEncode, BarcodeFormat.QR_CODE, width, height);
+//            用于将bitMatrix变成图片
+            BufferedImage image = MatrixToImageWriter.toBufferedImage(bitMatrix);
+
+            // 3. 转换为Base64字符串
+//            这是一个输出流
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+//            把对象写入到创建的输出流中
+            ImageIO.write(image, "png", baos);
+//            将字节数组转换成byte数组
+            byte[] imageBytes = baos.toByteArray();
+//            这个字符串可以方便地在文本环境中传输和存储图像数据
+            String base64Image = Base64.getEncoder().encodeToString(imageBytes);
+
+            // 4. 返回结果
+            result.put("success", true);
+            result.put("qrCodeUrl", "data:image/png;base64," + base64Image);
+            result.put("expireTime", System.currentTimeMillis() + 3600000); // 1小时有效期
+        } catch (Exception e) {
+            result.put("success", false);
+            result.put("message", "生成二维码失败: " + e.getMessage());
+        }
+        return result;
+    }
+}

+ 193 - 0
src/main/java/com/zhentao/user/controller/UserController.java

@@ -0,0 +1,193 @@
+package com.zhentao.user.controller;
+
+import com.zhentao.config.NullLogin;
+import com.zhentao.osspicture.OssUtil;
+import com.zhentao.tool.TokenUtils;
+import com.zhentao.user.domain.UserLogin;
+import com.zhentao.user.dto.*;
+import com.zhentao.user.service.UserLoginService;
+import com.zhentao.vo.Result;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Null;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+
+@RestController
+@RequestMapping("user")
+@RequiredArgsConstructor
+@Validated
+public class UserController {
+
+    @Autowired
+    public UserLoginService userLoginService;
+
+    @Autowired
+    public OssUtil ossUtil;
+
+
+    //注册
+    @PostMapping("/register")
+    @NullLogin
+    public Result Register(@RequestBody UserRegister userRegister){
+        return userLoginService.register(userRegister);
+    }
+
+    //验证码
+    @PostMapping("/code")
+    @NullLogin
+    public Result code(@RequestBody @Valid NoteDto noteDto) {
+//        System.err.println(noteDto);
+        return userLoginService.note(noteDto);
+    }
+    @PostMapping("/validateCode")
+    @NullLogin
+    public Result validateCode(@RequestBody @Valid NoteDto noteDto) {
+//        System.err.println(noteDto);
+        return userLoginService.validateCode(noteDto);
+    }
+    @PostMapping("/validateEmailCode")
+    @NullLogin
+    public Result validateEmailCode(@RequestBody @Valid EmailDto emailDto) {
+//        System.err.println(noteDto);
+        return userLoginService.validateEmailCode(emailDto);
+    }
+
+    //账号和密码进行登录
+    @PostMapping("/UserPassLogin")
+    @NullLogin
+    public Result UserPassLogin(@RequestBody @Valid UserPassDto userPassDto) {
+        Result result = userLoginService.UserPassLogin(userPassDto);
+        return result;
+    }
+    @PostMapping("/logout")
+    public Result logout(HttpServletRequest request){
+        return userLoginService.logout(request);
+    }
+    //    忘记密码
+    @PostMapping("/ForgetPass")
+    @NullLogin
+    public Result ForgetPass(@RequestBody @Valid ForgetPassDto forgetPassDto) {
+        return userLoginService.ForgetPass(forgetPassDto);
+    }
+
+    @PostMapping("/emailRegister")
+    @NullLogin
+    public Result emailRegister(@RequestBody @Valid EmailDto emailDto){
+        return userLoginService.emailRegister(emailDto);
+    }
+    /**
+     * 上传头像
+     */
+    @PostMapping("/upload-avatar")
+    @NullLogin
+    @CrossOrigin // 添加跨域支持
+    public Map<String, Object> uploadAvatar(@NotNull(message = "请选择上传文件")
+                                            @RequestParam("file") MultipartFile file) {
+        System.out.println("接收到文件上传请求:" + file.getOriginalFilename());
+        Map<String, Object> result = new HashMap<>();
+
+        // 检查文件大小
+        if (file.isEmpty()) {
+            System.err.println("上传文件为空");
+            result.put("success", false);
+            result.put("message", "上传文件不能为空");
+            return result;
+        }
+
+        // 检查文件大小(例如限制在5MB)
+        long maxSize = 5 * 1024 * 1024; // 5MB
+        if (file.getSize() > maxSize) {
+            System.err.println("文件大小超过限制:" + file.getSize());
+            result.put("success", false);
+            result.put("message", "文件大小不能超过5MB");
+            return result;
+        }
+
+        // 检查文件类型
+        String originalFilename = file.getOriginalFilename();
+        String fileExtension = originalFilename.substring(originalFilename.lastIndexOf(".") + 1).toLowerCase();
+        String[] allowedExtensions = {"jpg", "jpeg", "png", "gif", "bmp"};
+        boolean isAllowedExtension = false;
+        for (String ext : allowedExtensions) {
+            if (fileExtension.equals(ext)) {
+                isAllowedExtension = true;
+                break;
+            }
+        }
+        if (!isAllowedExtension) {
+            System.err.println("不支持的文件类型:" + fileExtension);
+            result.put("success", false);
+            result.put("message", "仅支持jpg, jpeg, png, gif, bmp格式的图片");
+            return result;
+        }
+
+        try {
+            // 上传到OSS
+            String url = ossUtil.uploadFile(file);
+
+            if (url == null) {
+                System.err.println("文件上传到OSS失败");
+                result.put("success", false);
+                result.put("message", "文件上传失败,请稍后重试");
+            } else {
+                System.out.println("文件上传成功:" + url);
+            result.put("success", true);
+            result.put("message", "上传成功");
+            result.put("url", url);
+            }
+        } catch (IOException e) {
+            System.err.println("文件上传异常:" + e.getMessage());
+            result.put("success", false);
+            result.put("message", "上传失败: " + e.getMessage());
+        }
+
+        return result;
+    }
+
+
+    //根据ID查询用户的信息
+    @GetMapping("getUserById")
+    @NullLogin
+    public Result getUserById(@RequestHeader("token") String token) {
+            String userIdFromToken = TokenUtils.getUserIdFromToken(token);
+            UserLogin userById = userLoginService.getUserById(Long.valueOf(userIdFromToken));
+            return Result.OK(userById, "查询成功");
+    }
+    @PostMapping("getUser")
+    @NullLogin
+    public Result getUser(@RequestBody UserInfo userInfo) {
+        System.err.println(userInfo);
+        UserLogin byId = userLoginService.getById(userInfo.getUserId());
+        return Result.OK(byId, "查询成功");
+    }
+    //查询好有-搜索
+    @GetMapping("searchFriends")
+    @NullLogin
+    public Result searchFriends(@RequestParam("keyword") String keyword) {
+        return Result.OK(userLoginService.searchFriends(keyword), "查询成功");
+    }
+
+
+    //  修改头像
+    @PostMapping("updateUserInfo")
+    @NullLogin
+    public Result updateUserInfo(@RequestHeader("token") String token, @RequestBody Userinfox userinfox) {
+        // 从 token 中获取当前登录用户的 ID
+        String userIdFromToken = TokenUtils.getUserIdFromToken(token);
+        userinfox.setId(Long.valueOf(userIdFromToken));
+        return userLoginService.updateUserInfo(userinfox);
+    }
+
+}

+ 79 - 0
src/main/java/com/zhentao/user/domain/UserLocation.java

@@ -0,0 +1,79 @@
+package com.zhentao.user.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.zhentao.handler.PointTypeHandler;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 用户位置记录表
+ * @TableName user_location
+ */
+@TableName(value ="user_location")
+@Data
+public class UserLocation implements Serializable {
+    /**
+     * 用户ID
+     */
+    @TableId
+    private Long userId;
+
+    /**
+     * 地理位置(经度,纬度)
+     */
+    @TableField(typeHandler = PointTypeHandler.class)
+    private String location;
+
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        UserLocation other = (UserLocation) that;
+        return (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId()))
+            && (this.getLocation() == null ? other.getLocation() == null : this.getLocation().equals(other.getLocation()))
+            && (this.getUpdateTime() == null ? other.getUpdateTime() == null : this.getUpdateTime().equals(other.getUpdateTime()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
+        result = prime * result + ((getLocation() == null) ? 0 : getLocation().hashCode());
+        result = prime * result + ((getUpdateTime() == null) ? 0 : getUpdateTime().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", userId=").append(userId);
+        sb.append(", location=").append(location);
+        sb.append(", updateTime=").append(updateTime);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 158 - 0
src/main/java/com/zhentao/user/domain/UserLogin.java

@@ -0,0 +1,158 @@
+package com.zhentao.user.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Data;
+
+/**
+ * 用户
+ * @TableName user_login
+ */
+@TableName(value ="user_login")
+@Data
+public class UserLogin implements Serializable {
+    /**
+     * 用户ID
+     */
+    @TableId
+    @JsonFormat(shape = JsonFormat.Shape.STRING)
+    private Long id;
+
+    /**
+     * 账号
+     */
+    private String userUsername;
+
+    /**
+     * 密码
+     */
+    @JsonIgnore
+    private String userPassword;
+
+    /**
+     * 盐
+     */
+    @JsonIgnore
+    private String salt;
+
+    /**
+     * 用户昵称
+     */
+    private String nickName;
+
+    /**
+     * 头像图片
+     */
+    private String avatar;
+
+    /**
+     * 用户名称
+     */
+    private String userName;
+
+    /**
+     * 性别1男2女3未知
+     */
+    private Integer gender;
+
+    /**
+     * 个性签名
+     */
+    private String userIntro;
+
+    /**
+     * 手机号
+     */
+    private String userMobile;
+
+    /**
+     * 身份证号
+     */
+    private String idenNo;
+
+    /**
+     * 等级描述
+     */
+    private String gradeDesc;
+
+    /**
+     * 生日
+     */
+    private Date birthDay;
+
+    /**
+     * 月
+     */
+    private Integer birthMonth;
+
+    /**
+     * 日
+     */
+    private Integer days;
+
+    /**
+     * 标签列表
+     */
+    private String labelList;
+
+    /**
+     * 状态1正常0锁定
+     */
+    private Integer status;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 创建时间
+     */
+    private Date createdTime;
+
+    /**
+     * 更新人
+     */
+    private String updatedBy;
+
+    /**
+     * 更新时间
+     */
+    private Date updatedTime;
+
+    /**
+     *
+     */
+    private String openId;
+
+    /**
+     *
+     */
+    private String sessionKey;
+
+    /**
+     *
+     */
+    private String uniId;
+
+    /**
+     * 邮箱
+     */
+    private String email;
+
+
+    /**
+     * 二维码
+     */
+    private String qwcode;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+}

+ 101 - 0
src/main/java/com/zhentao/user/domain/UserOnlineStatus.java

@@ -0,0 +1,101 @@
+package com.zhentao.user.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 用户在线状态表
+ * @TableName user_online_status
+ */
+@TableName(value ="user_online_status")
+@Data
+public class UserOnlineStatus implements Serializable {
+    /**
+     * 用户ID
+     */
+    @TableId
+    private Long userId;
+
+    /**
+     * 是否在线(0:离线,1:在线)
+     */
+    private Integer isOnline;
+
+    /**
+     * 上线时间
+     */
+    private Date onlineTime;
+
+    /**
+     * 最后心跳时间
+     */
+    private Date lastHeartbeatTime;
+
+    /**
+     * 设备信息
+     */
+    private String deviceInfo;
+
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        UserOnlineStatus other = (UserOnlineStatus) that;
+        return (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId()))
+            && (this.getIsOnline() == null ? other.getIsOnline() == null : this.getIsOnline().equals(other.getIsOnline()))
+            && (this.getOnlineTime() == null ? other.getOnlineTime() == null : this.getOnlineTime().equals(other.getOnlineTime()))
+            && (this.getLastHeartbeatTime() == null ? other.getLastHeartbeatTime() == null : this.getLastHeartbeatTime().equals(other.getLastHeartbeatTime()))
+            && (this.getDeviceInfo() == null ? other.getDeviceInfo() == null : this.getDeviceInfo().equals(other.getDeviceInfo()))
+            && (this.getUpdateTime() == null ? other.getUpdateTime() == null : this.getUpdateTime().equals(other.getUpdateTime()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
+        result = prime * result + ((getIsOnline() == null) ? 0 : getIsOnline().hashCode());
+        result = prime * result + ((getOnlineTime() == null) ? 0 : getOnlineTime().hashCode());
+        result = prime * result + ((getLastHeartbeatTime() == null) ? 0 : getLastHeartbeatTime().hashCode());
+        result = prime * result + ((getDeviceInfo() == null) ? 0 : getDeviceInfo().hashCode());
+        result = prime * result + ((getUpdateTime() == null) ? 0 : getUpdateTime().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", userId=").append(userId);
+        sb.append(", isOnline=").append(isOnline);
+        sb.append(", onlineTime=").append(onlineTime);
+        sb.append(", lastHeartbeatTime=").append(lastHeartbeatTime);
+        sb.append(", deviceInfo=").append(deviceInfo);
+        sb.append(", updateTime=").append(updateTime);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 17 - 0
src/main/java/com/zhentao/user/dto/EmailDto.java

@@ -0,0 +1,17 @@
+package com.zhentao.user.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+
+@Data
+public class EmailDto {
+    @NotBlank(message = "邮箱不能为空")
+    @Pattern(
+            regexp = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$",
+            message = "邮箱格式不正确"
+    )
+    private String email;
+    private String code;
+}

+ 16 - 0
src/main/java/com/zhentao/user/dto/ForgetPassDto.java

@@ -0,0 +1,16 @@
+package com.zhentao.user.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+@Data
+public class ForgetPassDto {
+//    手机号
+    @NotBlank(message = "手机号不能为空")
+    private String phone;
+    @NotBlank(message = "验证码不能为空")
+    private String code;
+    @NotBlank(message = "密码不能为空")
+    private String password;
+}

+ 53 - 0
src/main/java/com/zhentao/user/dto/FriendDto.java

@@ -0,0 +1,53 @@
+package com.zhentao.user.dto;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class FriendDto {
+    /**
+     * 用户ID
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 账号
+     */
+    private String userUsername;
+
+
+
+    /**
+     * 用户昵称
+     */
+    private String nickName;
+
+    /**
+     * 头像图片
+     */
+    private String avatar;
+
+    /**
+     * 用户名称
+     */
+    private String userName;
+
+    /**
+     * 性别1男2女3未知
+     */
+    private Integer gender;
+
+    /**
+     * 个性签名
+     */
+    private String userIntro;
+
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+}

+ 13 - 0
src/main/java/com/zhentao/user/dto/HeartbeatDto.java

@@ -0,0 +1,13 @@
+package com.zhentao.user.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class HeartbeatDto {
+    private Long userId;
+
+}

+ 15 - 0
src/main/java/com/zhentao/user/dto/LocationDto.java

@@ -0,0 +1,15 @@
+package com.zhentao.user.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class LocationDto {
+    private Double latitude;
+    private Double longitude;
+    private Integer limit;
+    private Double radius;
+}

+ 26 - 0
src/main/java/com/zhentao/user/dto/NearbyUserDTO.java

@@ -0,0 +1,26 @@
+package com.zhentao.user.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class NearbyUserDTO {
+    private Long userId;
+    private String username;
+    private String avatarUrl;
+    private double distance;
+    private String distanceUnit;
+    private boolean online;
+    private Location location;
+    @Data
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class Location {
+        private double longitude;
+        private double latitude;
+        private boolean isMyself;
+    }
+}

+ 19 - 0
src/main/java/com/zhentao/user/dto/NoteDto.java

@@ -0,0 +1,19 @@
+package com.zhentao.user.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+
+@Data
+public class NoteDto {
+
+    @NotBlank(message = "手机号不能为空")
+    @Pattern(regexp = "^1(3\\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\\d|9[0-35-9])\\d{8}$", message = "手机号格式不正确")
+    private String phone;
+    private String code;
+
+
+
+
+}

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