zhentao 3 weeks ago
commit
3633d0eebb
33 changed files with 1563 additions and 0 deletions
  1. 33 0
      .gitignore
  2. 143 0
      pom.xml
  3. 15 0
      src/main/java/com/zhentao/ImApplication.java
  4. 29 0
      src/main/java/com/zhentao/config/NettyConfig.java
  5. 11 0
      src/main/java/com/zhentao/config/NullLogin.java
  6. 43 0
      src/main/java/com/zhentao/config/RedissonConfig.java
  7. 19 0
      src/main/java/com/zhentao/config/interceptorconfig.java
  8. 28 0
      src/main/java/com/zhentao/enums/ApiServerException.java
  9. 6 0
      src/main/java/com/zhentao/enums/BaseExceptionEnum.java
  10. 15 0
      src/main/java/com/zhentao/exception/AsynException.java
  11. 55 0
      src/main/java/com/zhentao/exception/GlobalExceptionHandler.java
  12. 96 0
      src/main/java/com/zhentao/information/controller/MessageController.java
  13. 35 0
      src/main/java/com/zhentao/information/entity/ChatMessage.java
  14. 34 0
      src/main/java/com/zhentao/information/entity/Message.java
  15. 56 0
      src/main/java/com/zhentao/information/handler/WebSocketHandler.java
  16. 51 0
      src/main/java/com/zhentao/information/netty/NettyServer.java
  17. 24 0
      src/main/java/com/zhentao/information/repository/ChatMessageRepository.java
  18. 45 0
      src/main/java/com/zhentao/intercepoter/Userinterceptor.java
  19. 94 0
      src/main/java/com/zhentao/tool/TokenUtils.java
  20. 58 0
      src/main/java/com/zhentao/user/controller/UserController.java
  21. 141 0
      src/main/java/com/zhentao/user/domain/UserLogin.java
  22. 16 0
      src/main/java/com/zhentao/user/dto/ForgetPassDto.java
  23. 19 0
      src/main/java/com/zhentao/user/dto/NoteDto.java
  24. 18 0
      src/main/java/com/zhentao/user/dto/UserLoginDto.java
  25. 14 0
      src/main/java/com/zhentao/user/dto/UserPassDto.java
  26. 26 0
      src/main/java/com/zhentao/user/dto/UserRegister.java
  27. 18 0
      src/main/java/com/zhentao/user/mapper/UserLoginMapper.java
  28. 26 0
      src/main/java/com/zhentao/user/service/UserLoginService.java
  29. 265 0
      src/main/java/com/zhentao/user/service/impl/UserLoginServiceImpl.java
  30. 38 0
      src/main/java/com/zhentao/vo/Result.java
  31. 35 0
      src/main/resources/application.yml
  32. 44 0
      src/main/resources/mapper/UserLoginMapper.xml
  33. 13 0
      src/test/java/com/zhentao/ImApplicationTests.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/

+ 143 - 0
pom.xml

@@ -0,0 +1,143 @@
+<?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>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.12.1</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>
+            <version>8.0.28</version>
+        </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>
+    </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>
+                    <skip>true</skip>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>repackage</id>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

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

@@ -0,0 +1,15 @@
+package com.zhentao;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+@MapperScan("com.zhentao.*.mapper")
+public class ImApplication {
+
+    public static void main(String[] args){
+        SpringApplication.run(ImApplication.class, args);
+    }
+
+}

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

@@ -0,0 +1,29 @@
+package com.zhentao.config;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Netty服务器配置类
+ */
+@Configuration
+public class NettyConfig {
+
+    @Value("${netty.port:8888}")
+    private int port;
+
+    @Bean
+    public ServerBootstrap serverBootstrap() {
+        ServerBootstrap bootstrap = new ServerBootstrap();
+        bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup())
+                .channel(NioServerSocketChannel.class)
+                .option(ChannelOption.SO_BACKLOG, 128)
+                .childOption(ChannelOption.SO_KEEPALIVE, true);
+        return bootstrap;
+    }
+}

+ 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 {
+}

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

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

@@ -0,0 +1,28 @@
+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,"验证码错误"),
+    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, "系统参数异常");
+        }
+    }
+}

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

@@ -0,0 +1,96 @@
+package com.zhentao.information.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.zhentao.information.entity.ChatMessage;
+import com.zhentao.information.entity.Message;
+import com.zhentao.information.handler.WebSocketHandler;
+import com.zhentao.information.repository.ChatMessageRepository;
+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;
+import java.util.Map;
+
+/**
+ * 消息控制器
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/message")
+public class MessageController {
+
+    @Resource
+    private WebSocketHandler webSocketHandler;
+
+    @Resource
+    private ChatMessageRepository chatMessageRepository;
+
+    /**
+     * 发送消息接口
+     */
+    @PostMapping("/send")
+    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(message.getType());
+        chatMessage.setTimestamp(System.currentTimeMillis());
+        chatMessage.setIsRead(false);
+        chatMessage.setChatId(chatId);
+
+        // 保存消息到MongoDB
+        chatMessageRepository.save(chatMessage);
+
+        // 获取接收者的Channel
+        Map<String, ChannelHandlerContext> userChannelMap = webSocketHandler.getUserChannelMap();
+        ChannelHandlerContext toUserCtx = userChannelMap.get(message.getToUserId());
+
+        if (toUserCtx != null) {
+            // 发送消息给接收者
+            toUserCtx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message)));
+            return "消息已发送";
+        } else {
+            return "消息已保存,等待接收者上线";
+        }
+    }
+
+    /**
+     * 获取两个用户之间的聊天记录
+     */
+    @GetMapping("/history")
+    public List<ChatMessage> getChatHistory(@RequestParam String userId1, @RequestParam String userId2) {
+        String chatId = generateChatId(userId1, userId2);
+        return chatMessageRepository.findByChatId(chatId);
+    }
+
+    /**
+     * 获取用户的未读消息
+     */
+    @GetMapping("/unread")
+    public List<ChatMessage> getUnreadMessages(@RequestParam String userId) {
+        return chatMessageRepository.findByToUserIdAndIsReadFalse(userId);
+    }
+
+    /**
+     * 生成聊天ID
+     */
+    private String generateChatId(String userId1, String userId2) {
+        // 确保两个用户之间的聊天ID唯一,且与顺序无关
+        return userId1.compareTo(userId2) < 0 ?
+                userId1 + "_" + userId2 :
+                userId2 + "_" + userId1;
+    }
+}

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

@@ -0,0 +1,35 @@
+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 Integer type;
+
+    private Long timestamp;
+
+    private Boolean isRead;
+
+    // 复合索引:用于查询两个用户之间的聊天记录
+    @Indexed
+    private String chatId;
+}

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

@@ -0,0 +1,34 @@
+package com.zhentao.information.entity;
+
+import lombok.Data;
+
+/**
+ * 消息实体类
+ */
+@Data
+public class Message {
+    /**
+     * 发送者ID
+     */
+    private String fromUserId;
+
+    /**
+     * 接收者ID
+     */
+    private String toUserId;
+
+    /**
+     * 消息内容
+     */
+    private String content;
+
+    /**
+     * 消息类型
+     */
+    private Integer type;
+
+    /**
+     * 发送时间
+     */
+    private Long timestamp;
+}

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

@@ -0,0 +1,56 @@
+package com.zhentao.information.handler;
+
+import com.alibaba.fastjson.JSON;
+import com.zhentao.information.entity.Message;
+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 java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * WebSocket消息处理器
+ */
+@Slf4j
+@Component
+public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
+
+    // 用户ID和Channel的映射关系
+    private static final Map<String, ChannelHandlerContext> USER_CHANNEL_MAP = new ConcurrentHashMap<>();
+
+    /**
+     * 获取用户Channel映射
+     * @return 用户Channel映射
+     */
+    public Map<String, ChannelHandlerContext> getUserChannelMap() {
+        return USER_CHANNEL_MAP;
+    }
+
+    @Override
+    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {
+        String message = msg.text();
+        Message messageObj = JSON.parseObject(message, Message.class);
+
+        // 存储用户连接
+        USER_CHANNEL_MAP.put(messageObj.getFromUserId(), ctx);
+
+        // 获取接收者的Channel
+        ChannelHandlerContext toUserCtx = USER_CHANNEL_MAP.get(messageObj.getToUserId());
+        if (toUserCtx != null) {
+            // 发送消息给接收者
+            toUserCtx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(messageObj)));
+            log.info("消息已发送给用户: {}, 内容: {}", messageObj.getToUserId(), messageObj.getContent());
+        } else {
+            log.info("用户 {} 不在线", messageObj.getToUserId());
+        }
+    }
+
+    @Override
+    public void handlerRemoved(ChannelHandlerContext ctx) {
+        // 用户断开连接时,移除映射关系
+        USER_CHANNEL_MAP.entrySet().removeIf(entry -> entry.getValue().equals(ctx));
+    }
+}

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

@@ -0,0 +1,51 @@
+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服务器启动类
+ */
+@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);
+    }
+}

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

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

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

@@ -0,0 +1,45 @@
+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 requestURI = request.getRequestURI();
+//        if (requestURI.startsWith("/user/login")||requestURI.startsWith("/user/register")){
+//            return true;
+//        }
+        String token = request.getHeader("token");
+        if (token==null){
+            System.err.println("Token不能为空");
+            return false;
+        }
+        HandlerMethod handlerMethod = (HandlerMethod) handler;
+        NullLogin annotation = handlerMethod.getMethod().getAnnotation(NullLogin.class);
+        System.err.println("自定义注解"+annotation);
+        if (annotation!=null){
+            return true;
+        }
+        String userIdFromToken = TokenUtils.getUserIdFromToken(token);
+        System.err.println("解析后的ID:"+userIdFromToken);
+        String s = redisTemplate.opsForValue().get(userIdFromToken);
+        if (s!=null){
+            return true;
+        }else {
+            System.err.println("Token错误");
+            return false;
+        }
+    }
+}

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

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

@@ -0,0 +1,58 @@
+package com.zhentao.user.controller;
+
+import com.zhentao.config.NullLogin;
+import com.zhentao.user.dto.*;
+import com.zhentao.user.service.UserLoginService;
+import com.zhentao.vo.Result;
+import org.springframework.beans.factory.annotation.Autowired;
+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 javax.validation.Valid;
+
+
+@RestController
+@RequestMapping("user")
+public class UserController {
+
+    @Autowired
+    public UserLoginService userLoginService;
+
+    //注册
+    @PostMapping("/register")
+    @NullLogin
+    public Result Register(@RequestBody @Valid 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("/login")
+    @NullLogin
+    public Result login(@RequestBody @Valid UserLoginDto userLoginDto) {
+        return userLoginService.login(userLoginDto);
+    }
+
+    //账号和密码进行登录
+    @PostMapping("/UserPassLogin")
+    @NullLogin
+    public Result UserPassLogin(@RequestBody @Valid UserPassDto userPassDto) {
+        return userLoginService.UserPassLogin(userPassDto);
+    }
+
+//    忘记密码
+    @PostMapping("/ForgetPass")
+    @NullLogin
+    public Result ForgetPass(@RequestBody @Valid ForgetPassDto forgetPassDto) {
+        return userLoginService.ForgetPass(forgetPassDto);
+    }
+}

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

@@ -0,0 +1,141 @@
+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 lombok.Data;
+
+/**
+ * 用户
+ * @TableName user_login
+ */
+@TableName(value ="user_login")
+@Data
+public class UserLogin implements Serializable {
+    /**
+     * 用户ID
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 账号
+     */
+    private String userUsername;
+
+    /**
+     * 密码
+     */
+    private String userPassword;
+
+    /**
+     * 盐
+     */
+    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;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+}

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

+ 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-9]\\d{9}$",message = "手机号格式不正确" )
+    private String phone;
+
+
+
+
+
+}

+ 18 - 0
src/main/java/com/zhentao/user/dto/UserLoginDto.java

@@ -0,0 +1,18 @@
+package com.zhentao.user.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+
+@Data
+public class UserLoginDto {
+
+    @NotBlank(message = "手机号不能为空")
+    @Pattern(regexp = "^1[3-9]\\d{9}$",message = "手机号格式不正确" )
+    private String phone;
+
+    //验证码
+    @NotBlank(message = "验证码不能为空")
+    private String code;
+}

+ 14 - 0
src/main/java/com/zhentao/user/dto/UserPassDto.java

@@ -0,0 +1,14 @@
+package com.zhentao.user.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+@Data
+public class UserPassDto {
+
+    @NotBlank(message = "账号不能为空")
+    private String username;
+    @NotBlank(message = "密码不能为空")
+    private String password;
+}

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

@@ -0,0 +1,26 @@
+package com.zhentao.user.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+
+@Data
+public class UserRegister {
+    //手机号
+    @NotBlank(message = "手机号不能为空")
+    @Pattern(regexp = "^1[3-9]\\d{9}$",message = "手机号格式不正确" )
+    private String phone;
+
+    //验证码
+    @NotBlank(message = "验证码不能为空")
+    private String code;
+
+    //用户名
+    @NotBlank(message = "用户名不能为空")
+    private String username;
+
+    //密码
+    @NotBlank(message = "密码不能为空")
+    private String password;
+}

+ 18 - 0
src/main/java/com/zhentao/user/mapper/UserLoginMapper.java

@@ -0,0 +1,18 @@
+package com.zhentao.user.mapper;
+
+import com.zhentao.user.domain.UserLogin;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author 86183
+* @description 针对表【user_login(用户)】的数据库操作Mapper
+* @createDate 2025-06-03 18:38:51
+* @Entity com.zhentao.user.domain.UserLogin
+*/
+public interface UserLoginMapper extends BaseMapper<UserLogin> {
+
+}
+
+
+
+

+ 26 - 0
src/main/java/com/zhentao/user/service/UserLoginService.java

@@ -0,0 +1,26 @@
+package com.zhentao.user.service;
+
+import com.zhentao.user.domain.UserLogin;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhentao.user.dto.*;
+import com.zhentao.vo.Result;
+
+/**
+* @author 86183
+* @description 针对表【user_login(用户)】的数据库操作Service
+* @createDate 2025-06-03 18:38:51
+*/
+public interface UserLoginService extends IService<UserLogin> {
+    //注册
+    Result register(UserRegister userRegister);
+    //验证码
+    Result note(NoteDto noteDto);
+
+    //手机号登录
+    Result login(UserLoginDto userLoginDto);
+
+    //账号密码登录
+    Result UserPassLogin(UserPassDto userPassDto);
+    //    忘记密码
+    Result ForgetPass(ForgetPassDto forgetPassDto);
+}

+ 265 - 0
src/main/java/com/zhentao/user/service/impl/UserLoginServiceImpl.java

@@ -0,0 +1,265 @@
+package com.zhentao.user.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.enums.ApiServerException;
+import com.zhentao.exception.AsynException;
+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.user.mapper.UserLoginMapper;
+import com.zhentao.vo.Result;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.util.DigestUtils;
+
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+* @author 86183
+* @description 针对表【user_login(用户)】的数据库操作Service实现
+* @createDate 2025-06-03 18:38:51
+*/
+@Service
+public class UserLoginServiceImpl extends ServiceImpl<UserLoginMapper, UserLogin>
+    implements UserLoginService{
+    @Autowired
+    private UserLoginMapper userLoginMapper;
+    @Autowired
+    private RedissonClient redissonClient;
+    @Autowired
+    private StringRedisTemplate stringRedisTemplate;
+
+    //注册
+    @Override
+    public Result register(UserRegister userRegister) {
+        //打印用户注册的信息
+        System.err.println(userRegister);
+        //使用redisson客户端获取分布式锁,确保并发情况下用户注册的安全性
+        RLock lock = redissonClient.getLock(userRegister.getPhone() + userRegister.getPassword());
+        try {
+            boolean b = lock.tryLock(10, 20, TimeUnit.SECONDS);
+
+            if(b){
+                //用来判断验证码是否正确
+                String s = stringRedisTemplate.opsForValue().get(userRegister.getPhone());
+                System.err.println("redis取出来的验证码"+s);
+
+                //验证码不匹配就抛出异常
+                if(!s.equals(userRegister.getCode())){
+                    throw new AsynException(ApiServerException.NOTE_ERROR);
+                }
+                //根据手机号查询信息
+                QueryWrapper<UserLogin> queryWrapper=new QueryWrapper<>();
+                queryWrapper.eq("user_mobile",userRegister.getPhone());
+                UserLogin one = this.getOne(queryWrapper);
+
+                if(one==null){
+                    //新用户注册的流程
+                    UserLogin userLogin=new UserLogin();
+                    userLogin.setUserMobile(userRegister.getPhone());
+                    userLogin.setUserUsername(userRegister.getUsername());
+                    //随机字符串
+                    String uuid = String.valueOf(UUID.randomUUID());
+                    userLogin.setSalt(uuid);
+
+                    //md5加密
+                    String s1 = DigestUtils.md5DigestAsHex((uuid + userRegister.getPassword()).getBytes());
+                    userLogin.setUserPassword(s1);
+
+                    //生成唯一Id
+                    long l = IdUtil.getSnowflake(1, 1).nextId();
+                    //进行注册
+                    boolean save = this.save(userLogin);
+                    if(save){
+                        return Result.OK(save,"注册成功");
+                    }else{
+                        return Result.ERR(save,"注册失败");
+                    }
+                }else{
+                    //老用户更新信息流程
+                    one.setUserUsername(userRegister.getUsername());
+                    //随机字符串
+                    String uuid = String.valueOf(UUID.randomUUID());
+                    one.setSalt(uuid);
+                    //md5加密
+                    String s1 = DigestUtils.md5DigestAsHex((uuid + userRegister.getPassword()).getBytes());
+                    one.setUserPassword(s1);
+
+                    //进行更新
+                    boolean b1 = this.updateById(one);
+                    if(b1){
+                        return Result.OK(b1,"注册成功");
+                    }else{
+                        return Result.ERR(b1,"注册失败");
+                    }
+                }
+            }
+
+        }catch (InterruptedException e){
+            Thread.currentThread().interrupt();
+        }finally {
+            //释放锁
+            lock.unlock();
+        }
+        return null;
+    }
+
+
+    //验证码
+    @Override
+    public Result note(NoteDto noteDto) {
+        //随机生成六位数
+        int randomSixDigit=100000 + (int)(Math.random() * 900000);
+        System.err.println("手机号:"+noteDto.getPhone());
+        System.err.println("验证码:"+randomSixDigit);
+        stringRedisTemplate.opsForValue().set(noteDto.getPhone(),randomSixDigit+"");
+
+        return Result.OK(randomSixDigit,"发送成功");
+    }
+    //登录
+    /**
+     * 用户登录方法
+     * 使用Redis分布式锁来防止并发登录
+     * @param userLoginDto 用户登录信息,包含手机号和验证码
+     * @return 登录结果,包括token和提示信息
+     */
+    @Override
+    public Result login(UserLoginDto userLoginDto) {
+        // 获取Redis锁,锁的键为用户手机号,防止并发登录
+        RLock lock = redissonClient.getLock(userLoginDto.getPhone() + "phone");
+        try {
+            // 尝试获取锁,等待时间10秒,锁的过期时间为20秒
+            boolean b = lock.tryLock(10, 20, TimeUnit.SECONDS);
+            if (b) {
+                // 打印用户登录信息,用于调试
+                System.err.println(userLoginDto);
+                // 从Redis中获取验证码
+                String s = stringRedisTemplate.opsForValue().get(userLoginDto.getPhone());
+                // 打印从Redis中获取的验证码,用于调试
+                System.err.println("redis取出来的验证码" + s);
+                // 验证码不匹配则抛出异常
+                if (!s.equals(userLoginDto.getCode())) {
+                    throw new AsynException(ApiServerException.NOTE_ERROR);
+                }
+                try {
+                    // 查询数据库中是否存在该用户
+                    QueryWrapper<UserLogin> queryWrapper = new QueryWrapper<>();
+                    queryWrapper.eq("user_mobile", userLoginDto.getPhone());
+                    UserLogin one = this.getOne(queryWrapper);
+                    // 生成JWT token
+                    String token = TokenUtils.generateToken(one.getId() + "");
+                    // 返回登录成功结果,包含token
+                    return Result.OK(token, "登录成功");
+                } catch (NullPointerException e) {
+                    // 用户不存在时返回错误结果
+                    return Result.ERR("登录失败", "用户不存在");
+                }
+            }
+        } catch (InterruptedException e) {
+            // 中断当前线程,重新抛出自定义异常
+            Thread.currentThread().interrupt();
+            throw new AsynException(ApiServerException.NOTE_ERROR);
+        } finally {
+            // 释放锁
+            lock.unlock();
+        }
+        return null;
+    }
+    //账号密码登录
+    /**
+     * 使用用户名和密码进行用户登录的方法
+     * 该方法实现了用户认证和JWT令牌的生成
+     *
+     * @param userPassDto 包含用户名和密码的DTO对象
+     * @return 登录结果,包括登录状态和JWT令牌
+     */
+    @Override
+    public Result UserPassLogin(UserPassDto userPassDto) {
+        System.err.println(userPassDto);
+        // 获取Redisson客户端的锁对象,用于处理并发登录请求
+        RLock lock = redissonClient.getLock("user"+userPassDto.getUsername());
+        try {
+            // 尝试获取锁,设置等待和持有时间
+            boolean b = lock.tryLock(10, 20, TimeUnit.SECONDS);
+            if (b){
+                // 查询用户信息,根据用户名
+                QueryWrapper<UserLogin> queryWrapper = new QueryWrapper<>();
+                queryWrapper.eq("user_username",userPassDto.getUsername());
+                UserLogin one = this.getOne(queryWrapper);
+                // 如果用户不存在,抛出异常
+                if (one==null){
+                    throw new AsynException(ApiServerException.NULL_USERNAME);
+                }
+                // 获取用户盐值,用于密码加密
+                String salt = one.getSalt();
+                // 加密用户输入的密码,并与数据库中的密码进行比较
+                String s = DigestUtils.md5DigestAsHex((salt + userPassDto.getPassword()).getBytes());
+                if (!s.equals(one.getUserPassword())){
+                    throw new AsynException(ApiServerException.NULL_PASSWORD);
+                }
+                // 生成JWT令牌
+                String jwtToken = TokenUtils.generateToken(one.getId()+"");
+                // 返回登录成功结果和JWT令牌
+                return Result.OK("登录成功",jwtToken);
+            }else {
+                // 如果获取锁超时,返回错误信息
+                return Result.ERR("获取锁超时",null);
+            }
+        }catch (InterruptedException e){
+            // 如果线程被中断,恢复中断状态,并返回错误信息
+            System.err.println("所异常");
+            Thread.currentThread().interrupt();
+            return Result.ERR("线程被中断",null);
+        }finally {
+            // 释放锁
+            lock.unlock();
+        }
+    }
+    //忘记密码
+    @Override
+    public Result ForgetPass(ForgetPassDto forgetPassDto) {
+        RLock lock = redissonClient.getLock(forgetPassDto.getPhone() + "Phone");
+        try {
+            boolean b = lock.tryLock(10, 20, TimeUnit.SECONDS);
+            if (b) {
+                QueryWrapper<UserLogin> queryWrapper = new QueryWrapper<>();
+                queryWrapper.eq("user_mobile", forgetPassDto.getPhone());
+                UserLogin one = this.getOne(queryWrapper);
+                if (one == null) {
+                    return Result.ERR("用户不存在", null);
+                }
+                // 获取Redis中的验证码
+                String s = stringRedisTemplate.opsForValue().get(forgetPassDto.getPhone());
+                // 验证码不匹配则抛出异常
+                if (!s.equals(forgetPassDto.getCode())) {
+                    throw new AsynException(ApiServerException.NOTE_ERROR);
+                }
+                // 获取用户信息,根据手机号
+                String salt = one.getSalt();
+                String s1 = DigestUtils.md5DigestAsHex((salt + forgetPassDto.getPassword()).getBytes());
+                one.setUserPassword(s1);
+                boolean b1 = this.updateById(one);
+                if (b1){
+                    return Result.OK("修改成功", null);
+                }else {
+                    return Result.ERR("修改失败", null);
+                }
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        return null;
+    }
+
+}
+
+
+
+

+ 38 - 0
src/main/java/com/zhentao/vo/Result.java

@@ -0,0 +1,38 @@
+package com.zhentao.vo;
+
+import lombok.Data;
+
+@Data
+public class Result {
+    private Integer code;
+    private Object data;
+    private String msg;
+
+
+    public static Result OK(Object data,String msg) {
+        Result result = new Result();
+        result.setCode(200);
+        result.setData(data);
+        result.setMsg(msg);
+        return result;
+    }
+
+
+    public static Result ERR(Object data,String msg) {
+        Result result = new Result();
+        result.setCode(400);
+        result.setData(data);
+        result.setMsg(msg);
+        return result;
+    }
+
+    public static Result error(Integer code,Object data){
+        Result result=new Result();
+        result.setCode(code);
+        result.setData(data);
+        return result;
+    }
+
+
+
+}

+ 35 - 0
src/main/resources/application.yml

@@ -0,0 +1,35 @@
+server:
+  port: 8081
+netty:
+  port: 8888
+spring:
+  application:
+    name: im-server
+
+  # MySQL配置
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://47.110.46.22:3306/IM?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
+    username: root
+    password: Fengjaijia0610
+
+  # JPA配置
+  jpa:
+    database-platform: org.hibernate.dialect.MySQL8Dialect
+    show-sql: true
+    hibernate:
+      ddl-auto: update
+    properties:
+      hibernate:
+        format_sql: true
+
+  # MongoDB配置
+  data:
+    mongodb:
+      host: localhost
+      port: 27017
+      database: im_message_db
+  redis:
+    host: localhost
+    port: 6379
+    database: 0

+ 44 - 0
src/main/resources/mapper/UserLoginMapper.xml

@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zhentao.user.mapper.UserLoginMapper">
+
+    <resultMap id="BaseResultMap" type="com.zhentao.user.domain.UserLogin">
+            <id property="id" column="id" jdbcType="BIGINT"/>
+            <result property="userUsername" column="user_username" jdbcType="VARCHAR"/>
+            <result property="userPassword" column="user_password" jdbcType="VARCHAR"/>
+            <result property="salt" column="salt" jdbcType="VARCHAR"/>
+            <result property="nickName" column="nick_name" jdbcType="VARCHAR"/>
+            <result property="avatar" column="avatar" jdbcType="VARCHAR"/>
+            <result property="userName" column="user_name" jdbcType="VARCHAR"/>
+            <result property="gender" column="gender" jdbcType="INTEGER"/>
+            <result property="userIntro" column="user_intro" jdbcType="VARCHAR"/>
+            <result property="userMobile" column="user_mobile" jdbcType="VARCHAR"/>
+            <result property="idenNo" column="iden_no" jdbcType="VARCHAR"/>
+            <result property="gradeDesc" column="grade_desc" jdbcType="VARCHAR"/>
+            <result property="birthDay" column="birth_day" jdbcType="DATE"/>
+            <result property="birthMonth" column="birth_month" jdbcType="INTEGER"/>
+            <result property="days" column="days" jdbcType="INTEGER"/>
+            <result property="labelList" column="label_list" jdbcType="VARCHAR"/>
+            <result property="status" column="status" jdbcType="INTEGER"/>
+            <result property="remark" column="remark" jdbcType="VARCHAR"/>
+            <result property="createdTime" column="created_time" jdbcType="TIMESTAMP"/>
+            <result property="updatedBy" column="updated_by" jdbcType="VARCHAR"/>
+            <result property="updatedTime" column="updated_time" jdbcType="TIMESTAMP"/>
+            <result property="openId" column="open_id" jdbcType="VARCHAR"/>
+            <result property="sessionKey" column="session_key" jdbcType="VARCHAR"/>
+            <result property="uniId" column="uni_id" jdbcType="VARCHAR"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,user_username,user_password,
+        salt,nick_name,avatar,
+        user_name,gender,user_intro,
+        user_mobile,iden_no,grade_desc,
+        birth_day,birth_month,days,
+        label_list,status,remark,
+        created_time,updated_by,updated_time,
+        open_id,session_key,uni_id
+    </sql>
+</mapper>

+ 13 - 0
src/test/java/com/zhentao/ImApplicationTests.java

@@ -0,0 +1,13 @@
+package com.zhentao;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class ImApplicationTests {
+
+    @Test
+    void contextLoads() {
+    }
+
+}