zhangyu 9 hours ago
commit
a18a79fdf8
33 changed files with 1974 additions and 0 deletions
  1. 33 0
      .gitignore
  2. 152 0
      pom.xml
  3. 13 0
      src/main/java/com/example/course/ImApplication.java
  4. 18 0
      src/main/java/com/example/course/config/CorsConfig.java
  5. 57 0
      src/main/java/com/example/course/config/RedisConfig.java
  6. 31 0
      src/main/java/com/example/course/user/controller/MinioController.java
  7. 55 0
      src/main/java/com/example/course/user/controller/UserController.java
  8. 36 0
      src/main/java/com/example/course/user/controller/ValidateCode.java
  9. 9 0
      src/main/java/com/example/course/user/dto/LoginDto.java
  10. 9 0
      src/main/java/com/example/course/user/dto/PhoneDto.java
  11. 11 0
      src/main/java/com/example/course/user/dto/RegistDto.java
  12. 20 0
      src/main/java/com/example/course/user/mapper/UserMapper.java
  13. 20 0
      src/main/java/com/example/course/user/mapper/UserSettingMapper.java
  14. 101 0
      src/main/java/com/example/course/user/pojo/User.java
  15. 71 0
      src/main/java/com/example/course/user/pojo/UserSetting.java
  16. 24 0
      src/main/java/com/example/course/user/service/UserService.java
  17. 13 0
      src/main/java/com/example/course/user/service/UserSettingService.java
  18. 5 0
      src/main/java/com/example/course/user/service/ValidateCodeService.java
  19. 238 0
      src/main/java/com/example/course/user/service/impl/UserServiceImpl.java
  20. 22 0
      src/main/java/com/example/course/user/service/impl/UserSettingServiceImpl.java
  21. 23 0
      src/main/java/com/example/course/user/service/impl/ValidateCodeServiceImpl.java
  22. 45 0
      src/main/java/com/example/course/utils/AppHttpCodeEnum.java
  23. 119 0
      src/main/java/com/example/course/utils/AppJwtUtil.java
  24. 93 0
      src/main/java/com/example/course/utils/MinioUtils.java
  25. 288 0
      src/main/java/com/example/course/utils/RedisClient.java
  26. 163 0
      src/main/java/com/example/course/utils/ResponseResult.java
  27. 44 0
      src/main/java/com/example/course/utils/Result.java
  28. 136 0
      src/main/java/com/example/course/utils/SnowflakeIdGenerator.java
  29. 24 0
      src/main/resources/application.yml
  30. 34 0
      src/main/resources/mapper/UserMapper.xml
  31. 26 0
      src/main/resources/mapper/UserSettingMapper.xml
  32. 13 0
      src/test/java/com/example/course/ImApplicationTests.java
  33. 28 0
      src/test/java/com/example/course/Sockerservice.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/

+ 152 - 0
pom.xml

@@ -0,0 +1,152 @@
+<?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.example.course</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>
+    </properties>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.3.2.RELEASE</version>
+        <!--  使用合适的版本  -->
+    </parent>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <version>2.3.2.RELEASE</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <version>2.3.2.RELEASE</version>
+        </dependency>
+
+        <!--     mysql -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>8.0.33</version>
+        </dependency>
+
+        <!--     mybatis-plus -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>3.5.4</version>
+        </dependency>
+
+        <!--     lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.30</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.16</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+            <version>2.3.2.RELEASE</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid</artifactId>
+            <version>1.1.23</version>
+        </dependency>
+
+                <dependency>
+                    <groupId>io.minio</groupId>
+                    <artifactId>minio</artifactId>
+                    <version>7.1.0</version>
+                </dependency>
+
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.1</version>
+        </dependency>
+
+        <!--    Redis-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+            <version>2.3.2.RELEASE</version>
+        </dependency>
+
+        <!--        redisson分布式锁-->
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson</artifactId>
+            <version>3.14.1</version>
+        </dependency>
+
+        <!--        切面-->
+        <!--        <dependency>-->
+        <!--             <groupId>org.aspectj</groupId>-->
+        <!--             <artifactId>aspectjweaver</artifactId>-->
+        <!--            <version>1.9.19</version>-->
+        <!--        </dependency>-->
+
+        <!--        okhttp-->
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>4.9.3</version>
+        </dependency>
+
+        <!--                fastjson-->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>2.0.41</version>
+        </dependency>
+
+        <!--     commons-lang3   -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.17.0</version>
+        </dependency>
+
+        <!--                netty-all-->
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+            <version>4.1.84.Final</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>8</source>
+                    <target>8</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 13 - 0
src/main/java/com/example/course/ImApplication.java

@@ -0,0 +1,13 @@
+package com.example.course;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ImApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(ImApplication.class, args);
+    }
+
+}

+ 18 - 0
src/main/java/com/example/course/config/CorsConfig.java

@@ -0,0 +1,18 @@
+package com.example.course.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class CorsConfig implements WebMvcConfigurer {
+
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+            .allowedOrigins("*")
+            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
+            .maxAge(3600);
+    }
+
+}

+ 57 - 0
src/main/java/com/example/course/config/RedisConfig.java

@@ -0,0 +1,57 @@
+package com.example.course.config;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import org.springframework.context.annotation.Bean;
+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;
+import org.springframework.stereotype.Component;
+
+import java.text.SimpleDateFormat;
+
+@Component
+public class RedisConfig {
+
+    @Bean
+    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
+        redisTemplate.setConnectionFactory(connectionFactory);
+
+        // Key/HashKey 使用字符串序列化
+        StringRedisSerializer stringSerializer = new StringRedisSerializer();
+        redisTemplate.setKeySerializer(stringSerializer);
+        redisTemplate.setHashKeySerializer(stringSerializer);
+
+        // Value/HashValue 使用 JSON 序列化(带类型信息)
+        GenericJackson2JsonRedisSerializer jsonSerializer = createJsonSerializer();
+        redisTemplate.setValueSerializer(jsonSerializer);
+        redisTemplate.setHashValueSerializer(jsonSerializer);
+
+        redisTemplate.afterPropertiesSet();
+        return redisTemplate;
+    }
+
+    /**
+     * 创建支持类型信息的 JSON 序列化器
+     */
+    private GenericJackson2JsonRedisSerializer createJsonSerializer() {
+        ObjectMapper objectMapper = new ObjectMapper();
+
+        // 启用默认类型信息(解决嵌套对象反序列化问题)
+        objectMapper.activateDefaultTyping(
+                objectMapper.getPolymorphicTypeValidator(),
+                ObjectMapper.DefaultTyping.NON_FINAL,
+                JsonTypeInfo.As.PROPERTY
+        );
+
+        // 配置日期格式(根据实际需求调整)
+        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+        objectMapper.registerModule(new JavaTimeModule());
+        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
+        return new GenericJackson2JsonRedisSerializer(objectMapper);
+    }
+}

+ 31 - 0
src/main/java/com/example/course/user/controller/MinioController.java

@@ -0,0 +1,31 @@
+package com.example.course.user.controller;
+import com.example.course.utils.MinioUtils;
+import com.example.course.utils.Result;
+import org.springframework.beans.factory.annotation.Autowired;
+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;
+
+@RestController
+@RequestMapping("minio")
+public class MinioController {
+    @Autowired
+    private MinioUtils minioUtils;
+
+    /**
+     * 上传图片
+     * @param file
+     * @return
+     */
+    @RequestMapping("upload")
+    public Result upload(@RequestParam("file") MultipartFile file){
+        try {
+            String s = minioUtils.uploadImage(file);
+            return new Result(true,"上传成功",s);
+        }catch (Exception e){
+            e.printStackTrace();
+            return new Result(false,"上传失败");
+        }
+    }
+}

+ 55 - 0
src/main/java/com/example/course/user/controller/UserController.java

@@ -0,0 +1,55 @@
+package com.example.course.user.controller;
+
+import com.example.course.user.dto.LoginDto;
+import com.example.course.user.dto.PhoneDto;
+import com.example.course.user.dto.RegistDto;
+import com.example.course.user.service.UserService;
+import com.example.course.utils.ResponseResult;
+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;
+
+@RestController
+@RequestMapping("/user")
+public class UserController {
+    @Autowired
+    private UserService userService;
+    /**
+     * 登录
+     * @param
+     * @return
+     */
+    @RequestMapping("login")
+    public ResponseResult login(@RequestBody LoginDto loginDto){
+        return userService.login(loginDto);
+    }
+
+    /**
+     * 手机号登录
+     * @param phoneDto
+     * @return
+     */
+    @RequestMapping("phonelogin")
+    public ResponseResult phonelogin(@RequestBody PhoneDto phoneDto){
+        return userService.phonelogin(phoneDto);
+    }
+
+    /**
+     * 注册
+     * @param registDto
+     * @return
+     */
+    @PostMapping("/regist")
+    public ResponseResult regist(@RequestBody RegistDto registDto){
+        return userService.regist(registDto);
+    }
+    /**
+     * 退出登录
+     */
+    @RequestMapping("/exit")
+    public ResponseResult exit(){
+        return userService.exit();
+    }
+}

+ 36 - 0
src/main/java/com/example/course/user/controller/ValidateCode.java

@@ -0,0 +1,36 @@
+package com.example.course.user.controller;
+import com.example.course.user.service.ValidateCodeService;
+import com.example.course.utils.RedisClient;
+import com.example.course.utils.Result;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+
+@RestController
+@RequestMapping("/validateCode")
+public class ValidateCode {
+
+
+    @Autowired
+    private ValidateCodeService validateCodeService;
+
+    @Autowired
+    private RedisClient redisClient;
+
+    /**
+     * 生成验证码
+     * @param phone
+     * @return
+     */
+    @RequestMapping("/send4Order")
+    public Result send4Order(@RequestParam("phone") String phone) {
+        Integer code = validateCodeService.send4Order(phone);
+        String key = "CODE:" + phone;
+        redisClient.set(key, code);
+        redisClient.expire(key, 60);
+        System.out.println("您的验证码为:------"+code);
+        return new Result(true,"验证码发送成功");
+    }
+}

+ 9 - 0
src/main/java/com/example/course/user/dto/LoginDto.java

@@ -0,0 +1,9 @@
+package com.example.course.user.dto;
+
+import lombok.Data;
+
+@Data
+public class LoginDto {
+    private String username;
+    private String password;
+}

+ 9 - 0
src/main/java/com/example/course/user/dto/PhoneDto.java

@@ -0,0 +1,9 @@
+package com.example.course.user.dto;
+
+import lombok.Data;
+
+@Data
+public class PhoneDto {
+    private String phone;
+    private String code;
+}

+ 11 - 0
src/main/java/com/example/course/user/dto/RegistDto.java

@@ -0,0 +1,11 @@
+package com.example.course.user.dto;
+
+import lombok.Data;
+
+@Data
+public class RegistDto {
+    private String username;
+    private String password;
+    private String phone;
+    private String code;
+}

+ 20 - 0
src/main/java/com/example/course/user/mapper/UserMapper.java

@@ -0,0 +1,20 @@
+package com.example.course.user.mapper;
+
+import com.example.course.user.pojo.User;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+* @author 张大宇
+* @description 针对表【user(用户信息表)】的数据库操作Mapper
+* @createDate 2025-05-19 18:56:29
+* @Entity com.example.course.user.pojo.User
+*/
+@Mapper
+public interface UserMapper extends BaseMapper<User> {
+
+}
+
+
+
+

+ 20 - 0
src/main/java/com/example/course/user/mapper/UserSettingMapper.java

@@ -0,0 +1,20 @@
+package com.example.course.user.mapper;
+
+import com.example.course.user.pojo.UserSetting;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+* @author 张大宇
+* @description 针对表【user_setting(用户设置表)】的数据库操作Mapper
+* @createDate 2025-05-19 18:56:29
+* @Entity com.example.course.user.pojo.UserSetting
+*/
+@Mapper
+public interface UserSettingMapper extends BaseMapper<UserSetting> {
+
+}
+
+
+
+

+ 101 - 0
src/main/java/com/example/course/user/pojo/User.java

@@ -0,0 +1,101 @@
+package com.example.course.user.pojo;
+
+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
+ */
+@TableName(value ="user")
+@Data
+public class User implements Serializable {
+    /**
+     * 用户ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 盐
+     */
+    private String sale;
+
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 加密后的密码
+     */
+    private String password;
+
+    /**
+     * 手机号
+     */
+    private String phone;
+
+    /**
+     * 邮箱
+     */
+    private String email;
+
+    /**
+     * 1-直接加 2-同意后加好友
+     */
+    private Integer joinType;
+
+    /**
+     * 头像URL
+     */
+    private String avatar;
+
+    /**
+     * 个性签名
+     */
+    private String signature;
+
+    /**
+     * 性别(0:未知,1:男,2:女)
+     */
+    private Integer gender;
+
+    /**
+     * 生日
+     */
+    private Date birthday;
+
+    /**
+     * 用户状态(0:禁用,1:正常,2:隐身)
+     */
+    private Integer status;
+
+    /**
+     * 最后登录时间
+     */
+    private Date lastLoginTime;
+
+    /**
+     * 退出时间
+     */
+    private Date lastExitTime;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+}

+ 71 - 0
src/main/java/com/example/course/user/pojo/UserSetting.java

@@ -0,0 +1,71 @@
+package com.example.course.user.pojo;
+
+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_setting
+ */
+@TableName(value ="user_setting")
+@Data
+public class UserSetting implements Serializable {
+    /**
+     * 设置ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 是否开启通知(0:关闭,1:开启)
+     */
+    private Integer notificationEnabled;
+
+    /**
+     * 是否开启声音(0:关闭,1:开启)
+     */
+    private Integer soundEnabled;
+
+    /**
+     * 是否开启震动(0:关闭,1:开启)
+     */
+    private Integer vibrateEnabled;
+
+    /**
+     * 是否显示消息预览(0:关闭,1:开启)
+     */
+    private Integer messagePreviewEnabled;
+
+    /**
+     * 是否自动下载媒体(0:关闭,1:开启)
+     */
+    private Integer autoDownloadMedia;
+
+    /**
+     * 主题(light:浅色,dark:深色)
+     */
+    private String theme;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+}

+ 24 - 0
src/main/java/com/example/course/user/service/UserService.java

@@ -0,0 +1,24 @@
+package com.example.course.user.service;
+
+import com.example.course.user.dto.LoginDto;
+import com.example.course.user.dto.PhoneDto;
+import com.example.course.user.dto.RegistDto;
+import com.example.course.user.pojo.User;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.example.course.utils.ResponseResult;
+
+/**
+* @author 张大宇
+* @description 针对表【user(用户信息表)】的数据库操作Service
+* @createDate 2025-05-19 18:56:29
+*/
+public interface UserService extends IService<User> {
+
+    ResponseResult login(LoginDto loginDto);
+
+    ResponseResult phonelogin(PhoneDto phoneDto);
+
+    ResponseResult regist(RegistDto registDto);
+
+    ResponseResult exit();
+}

+ 13 - 0
src/main/java/com/example/course/user/service/UserSettingService.java

@@ -0,0 +1,13 @@
+package com.example.course.user.service;
+
+import com.example.course.user.pojo.UserSetting;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author 张大宇
+* @description 针对表【user_setting(用户设置表)】的数据库操作Service
+* @createDate 2025-05-19 18:56:29
+*/
+public interface UserSettingService extends IService<UserSetting> {
+
+}

+ 5 - 0
src/main/java/com/example/course/user/service/ValidateCodeService.java

@@ -0,0 +1,5 @@
+package com.example.course.user.service;
+
+public interface ValidateCodeService {
+    public Integer send4Order(String phone);
+}

+ 238 - 0
src/main/java/com/example/course/user/service/impl/UserServiceImpl.java

@@ -0,0 +1,238 @@
+package com.example.course.user.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.example.course.user.dto.LoginDto;
+import com.example.course.user.dto.PhoneDto;
+import com.example.course.user.dto.RegistDto;
+import com.example.course.user.pojo.User;
+import com.example.course.user.service.UserService;
+import com.example.course.user.mapper.UserMapper;
+import com.example.course.utils.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.DigestUtils;
+import org.springframework.util.StringUtils;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * @author 张大宇
+ * @description 针对表【user(用户信息表)】的数据库操作Service实现
+ * @createDate 2025-05-19 18:56:29
+ */
+@Service
+public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
+
+    private static final Logger logger = LoggerFactory.getLogger(UserService.class);
+
+    @Autowired
+    private UserMapper userMapper;
+
+    @Autowired
+    private RedisClient redisClient;
+
+    /**
+     * 统一获取验证码方法
+     * @param phone 手机号
+     * @return 验证码
+     */
+    private Object getVerificationCode(String phone) {
+        String key = "CODE:" + phone;
+        return redisClient.get(key);
+    }
+
+    /**
+     * 统一删除验证码方法
+     * @param phone 手机号
+     */
+    private void deleteVerificationCode(String phone) {
+        String key = "CODE:" + phone;
+        redisClient.del(key);
+    }
+
+    /**
+     * 账号密码登录
+     * @param loginDto
+     * @return
+     */
+    @Override
+    public ResponseResult login(LoginDto loginDto) {
+        if (StringUtils.isEmpty(loginDto.getUsername()) || StringUtils.isEmpty(loginDto.getPassword())) {
+            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_REQUIRE);
+        }
+        QueryWrapper<User> wrapper = new QueryWrapper<>();
+        User selectOne = userMapper.selectOne(wrapper.lambda().eq(User::getUsername, loginDto.getUsername()));
+        if (selectOne == null) {
+            return ResponseResult.errorResult(AppHttpCodeEnum.AP_USER_DATA_NOT_EXIST);
+        }
+
+        String salt = selectOne.getSale();
+        String password = loginDto.getPassword();
+        String md5 = DigestUtils.md5DigestAsHex((password + salt).getBytes(StandardCharsets.UTF_8));
+        if (!md5.equals(selectOne.getPassword())) {
+            return ResponseResult.errorResult(202, "密码错误");
+        }
+        String token = AppJwtUtil.getToken(selectOne.getId().longValue());
+        redisClient.set("token", token);
+        HashMap<String, String> map = new HashMap<>();
+        map.put("token", token);
+        map.put("name", selectOne.getUsername());
+        return ResponseResult.okResult(map);
+    }
+
+    /**
+     * 手机号验证码登录
+     * @param phoneDto
+     * @return
+     */
+    @Override
+    public ResponseResult phonelogin(PhoneDto phoneDto) {
+        // 参数校验
+        if (phoneDto.getPhone() == null ||!phoneDto.getPhone().matches("1[3-9]\\d{9}")) {
+            return ResponseResult.errorResult(400, "手机号格式错误");
+        }
+
+        // 获取验证码
+        Object codeObj = getVerificationCode(phoneDto.getPhone());
+        if (codeObj == null) {
+            logger.info("手机号 {} 验证码已过期", phoneDto.getPhone());
+            return ResponseResult.errorResult(201, "验证码已过期");
+        }
+
+        // 验证验证码
+        String codeStr = String.valueOf(codeObj);
+        String userCode = phoneDto.getCode();
+        if (userCode == null ||!userCode.matches("\\d{4}")) {
+            logger.info("手机号 {} 输入的验证码格式错误", phoneDto.getPhone());
+            return ResponseResult.errorResult(201, "验证码格式错误");
+        }
+
+        if (!codeStr.equals(userCode)) {
+            logger.info("手机号 {} 验证码错误,Redis: {}, 用户输入: {}",
+                    phoneDto.getPhone(), codeStr, userCode);
+            return ResponseResult.errorResult(201, "验证码错误");
+        }
+
+        // 验证通过后删除验证码
+        deleteVerificationCode(phoneDto.getPhone());
+
+        // 查询用户是否存在
+        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(User::getPhone, phoneDto.getPhone());
+        User existingUser = userMapper.selectOne(queryWrapper);
+
+        // 用户不存在,创建新用户
+        if (existingUser == null) {
+            User newUser = new User();
+            newUser.setId(SnowflakeIdGenerator.getSnowId());
+            newUser.setPhone(phoneDto.getPhone());
+
+            // 生成随机用户名(格式:用户+时间戳后4位)
+            String randomUsername = "用户" + System.currentTimeMillis() % 10000;
+            newUser.setUsername(randomUsername);
+
+            try {
+                userMapper.insert(newUser);
+                logger.info("新用户创建成功,ID:{},手机号:{}", newUser.getId(), phoneDto.getPhone());
+                return generateLoginResponse(newUser);
+            } catch (Exception e) {
+                logger.error("用户创建失败,手机号:{}", phoneDto.getPhone(), e);
+                return ResponseResult.errorResult(500, "用户注册失败,请重试");
+            }
+        }
+
+        // 用户已存在,直接登录
+        logger.info("用户登录成功,ID:{},手机号:{}", existingUser.getId(), phoneDto.getPhone());
+        return generateLoginResponse(existingUser);
+    }
+
+    private ResponseResult generateLoginResponse(User user) {
+        // 存储用户ID到Redis
+        String uidKey = "USER:UID:" + user.getId();
+        redisClient.set(uidKey, user.getId());
+        redisClient.expire(uidKey, 3600); // 1小时有效期
+
+        // 生成JWT Token
+        String token = AppJwtUtil.getToken(user.getId());
+
+        // 存储Token到Redis(可选,用于Token黑名单等场景)
+        redisClient.set("token", token);
+        redisClient.expire(token, 3600);
+
+
+        // 返回结果
+        Map<String, Object> resultMap = new HashMap<>();
+        resultMap.put("token", token);
+        resultMap.put("name", user.getUsername());
+        resultMap.put("userId", user.getId());
+
+        return ResponseResult.okResult(resultMap);
+    }
+
+    /**
+     * 注册
+     * @param registDto
+     * @return
+     */
+    @Override
+    public ResponseResult regist(RegistDto registDto) {
+        // 获取验证码
+        Object code = getVerificationCode(registDto.getPhone());
+        if (code == null) {
+            return new ResponseResult(201, "验证码已过期");
+        }
+        String codeStr = String.valueOf(code);
+        String userCode = registDto.getCode();
+        if (!codeStr.equals(userCode)) {
+            return new ResponseResult(201, "验证码错误");
+        }
+        // 删除已使用的验证码
+        deleteVerificationCode(registDto.getPhone());
+
+        // 先判断账号是否存在
+        QueryWrapper<User> usernameWrapper = new QueryWrapper<>();
+        usernameWrapper.lambda().eq(User::getUsername, registDto.getUsername());
+        User user1 = userMapper.selectOne(usernameWrapper);
+        if (user1 != null) {
+            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_EXIST, "账号已存在");
+        }
+
+        // 判断手机号是否存在
+        QueryWrapper<User> phoneWrapper = new QueryWrapper<>();
+        phoneWrapper.lambda().eq(User::getPhone, registDto.getPhone());
+        User user2 = userMapper.selectOne(phoneWrapper);
+        if (user2 != null) {
+            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_EXIST, "手机号已存在");
+        }
+
+        // 注册用户
+        User user = new User();
+        user.setUsername(registDto.getUsername());
+        user.setPhone(registDto.getPhone());
+        String salt = UUID.randomUUID().toString();
+        user.setSale(salt);
+        user.setPassword(DigestUtils.md5DigestAsHex((registDto.getPassword() + salt).getBytes(StandardCharsets.UTF_8)));
+        user.setId(SnowflakeIdGenerator.getSnowId());
+
+        userMapper.insert(user);
+        redisClient.set("uid", user.getId());
+        redisClient.expire("uid", 60 * 60);
+
+        return ResponseResult.okResult(200, "注册成功");
+    }
+
+    @Override
+    public ResponseResult exit() {
+        if (redisClient.exists("uid")) {
+            redisClient.del("uid");
+            return ResponseResult.okResult(200, "退出成功");
+        }
+        return null;
+    }
+}

+ 22 - 0
src/main/java/com/example/course/user/service/impl/UserSettingServiceImpl.java

@@ -0,0 +1,22 @@
+package com.example.course.user.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.example.course.user.pojo.UserSetting;
+import com.example.course.user.service.UserSettingService;
+import com.example.course.user.mapper.UserSettingMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author 张大宇
+* @description 针对表【user_setting(用户设置表)】的数据库操作Service实现
+* @createDate 2025-05-19 18:56:29
+*/
+@Service
+public class UserSettingServiceImpl extends ServiceImpl<UserSettingMapper, UserSetting>
+    implements UserSettingService{
+
+}
+
+
+
+

+ 23 - 0
src/main/java/com/example/course/user/service/impl/ValidateCodeServiceImpl.java

@@ -0,0 +1,23 @@
+package com.example.course.user.service.impl;
+
+
+
+import com.example.course.user.service.ValidateCodeService;
+import org.springframework.stereotype.Service;
+
+import java.util.Random;
+
+
+@Service
+public class ValidateCodeServiceImpl implements ValidateCodeService {
+    @Override
+    public Integer send4Order(String phone) {
+        String substring = phone.substring(2, 6);
+        Random random = new Random();
+        int j = random.nextInt(Integer.valueOf(substring));
+        if (j < 1000) {
+            j += 1000;
+        }
+        return j;
+    }
+}

+ 45 - 0
src/main/java/com/example/course/utils/AppHttpCodeEnum.java

@@ -0,0 +1,45 @@
+package com.example.course.utils;
+
+public enum AppHttpCodeEnum {
+
+    // 成功段0
+    SUCCESS(200,"操作成功"),
+    // 登录段1~50
+    NEED_LOGIN(1,"需要登录后操作"),
+    LOGIN_PASSWORD_ERROR(2,"密码错误"),
+    // TOKEN50~100
+    TOKEN_INVALID(50,"无效的TOKEN"),
+    TOKEN_EXPIRE(51,"TOKEN已过期"),
+    TOKEN_REQUIRE(52,"TOKEN是必须的"),
+    // SIGN验签 100~120
+    SIGN_INVALID(100,"无效的SIGN"),
+    SIG_TIMEOUT(101,"SIGN已过期"),
+    // 参数错误 500~1000
+    PARAM_REQUIRE(500,"缺少参数"),
+    PARAM_INVALID(501,"无效参数"),
+    PARAM_IMAGE_FORMAT_ERROR(502,"图片格式有误"),
+    SERVER_ERROR(503,"服务器内部错误"),
+    // 数据错误 1000~2000
+    DATA_EXIST(1000,"数据已经存在"),
+    AP_USER_DATA_NOT_EXIST(1001,"ApUser数据不存在"),
+    DATA_NOT_EXIST(1002,"数据不存在"),
+    // 数据错误 3000~3500
+    NO_OPERATOR_AUTH(3000,"无权限操作"),
+    NEED_ADMIND(3001,"需要管理员权限");
+
+    int code;
+    String errorMessage;
+
+    AppHttpCodeEnum(int code, String errorMessage){
+        this.code = code;
+        this.errorMessage = errorMessage;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+}

+ 119 - 0
src/main/java/com/example/course/utils/AppJwtUtil.java

@@ -0,0 +1,119 @@
+package com.example.course.utils;
+
+import io.jsonwebtoken.*;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.util.*;
+
+public class AppJwtUtil {
+
+    // TOKEN的有效期一天(S)
+    private static final int TOKEN_TIME_OUT = 3_600;
+    // 加密KEY
+    private static final String TOKEN_ENCRY_KEY = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";
+    // 最小刷新间隔(S)
+    private static final int REFRESH_TIME = 300;
+
+    // 生产ID
+    public static String getToken(Long id){
+        Map<String, Object> claimMaps = new HashMap<>();
+        claimMaps.put("id",id);
+        long currentTime = System.currentTimeMillis();
+        return Jwts.builder()
+                .setId(UUID.randomUUID().toString())
+                .setIssuedAt(new Date(currentTime))  //签发时间
+                .setSubject("system")  //说明
+                .setIssuer("") //签发者信息
+                .setAudience("app")  //接收用户
+                .compressWith(CompressionCodecs.GZIP)  //数据压缩方式
+                .signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式
+                .setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000))  //过期时间戳
+                .addClaims(claimMaps) //cla信息
+                .compact();
+    }
+
+    /**
+     * 获取token中的claims信息
+     *
+     * @param token
+     * @return
+     */
+    private static Jws<Claims> getJws(String token) {
+            return Jwts.parser()
+                    .setSigningKey(generalKey())
+                    .parseClaimsJws(token);
+    }
+
+    /**
+     * 获取payload body信息
+     *
+     * @param token
+     * @return
+     */
+    public static Claims getClaimsBody(String token) {
+        try {
+            return getJws(token).getBody();
+        }catch (ExpiredJwtException e){
+            return null;
+        }
+    }
+
+    /**
+     * 获取hearder body信息
+     *
+     * @param token
+     * @return
+     */
+    public static JwsHeader getHeaderBody(String token) {
+        return getJws(token).getHeader();
+    }
+
+    /**
+     * 是否过期
+     *
+     * @param claims
+     * @return -1:有效,0:有效,1:过期,2:过期
+     */
+    public static int verifyToken(Claims claims) {
+        if(claims==null){
+            return 1;
+        }
+        try {
+            claims.getExpiration()
+                    .before(new Date());
+            // 需要自动刷新TOKEN
+            if((claims.getExpiration().getTime()-System.currentTimeMillis())>REFRESH_TIME*1000){
+                return -1;
+            }else {
+                return 0;
+            }
+        } catch (ExpiredJwtException ex) {
+            return 1;
+        }catch (Exception e){
+            return 2;
+        }
+    }
+
+    /**
+     * 由字符串生成加密key
+     *
+     * @return
+     */
+    public static SecretKey generalKey() {
+        byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCRY_KEY.getBytes());
+        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
+        return key;
+    }
+
+    public static void main(String[] args) {
+       /* Map map = new HashMap();
+        map.put("id","11");*/
+        System.out.println(AppJwtUtil.getToken(1102L));
+        Jws<Claims> jws = AppJwtUtil.getJws("eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWLQQqEMAwA_5KzhURNt_qb1KZYQSi0wi6Lf9942NsMw3zh6AVW2DYmDGl2WabkZgreCaM6VXzhFBfJMcMARTqsxIG9Z888QLui3e3Tup5Pb81013KKmVzJTGo11nf9n8v4nMUaEY73DzTabjmDAAAA.4SuqQ42IGqCgBai6qd4RaVpVxTlZIWC826QA9kLvt9d-yVUw82gU47HDaSfOzgAcloZedYNNpUcd18Ne8vvjQA");
+        Claims claims = jws.getBody();
+        System.out.println(claims.get("id"));
+
+    }
+
+}

+ 93 - 0
src/main/java/com/example/course/utils/MinioUtils.java

@@ -0,0 +1,93 @@
+package com.example.course.utils;
+
+import io.minio.*;
+import io.minio.errors.*;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.UUID;
+
+@Component
+public class MinioUtils {
+
+    @Value("${minio.endpoint}")
+    private String endpoint;
+
+    @Value("${minio.accessKey}")
+    private String accessKey;
+
+    @Value("${minio.accessSecret}")
+    private String accessSecret;
+
+    @Value("${minio.bucketName}")
+    private String bucketName;
+
+    private MinioClient minioClient;
+
+    public MinioUtils() {
+    }
+
+    private MinioClient getMinioClient() {
+        if (minioClient == null) {
+            minioClient = MinioClient.builder()
+                    .endpoint(endpoint)
+                    .credentials(accessKey, accessSecret)
+                    .build();
+        }
+        return minioClient;
+    }
+
+    /**
+     * 生成随机文件名
+     * @param originalFileName 原始文件名
+     * @return 随机文件名
+     */
+    private String generateRandomFileName(String originalFileName) {
+        // 获取文件扩展名
+        String extension = "";
+        int dotIndex = originalFileName.lastIndexOf('.');
+        if (dotIndex > 0) {
+            extension = originalFileName.substring(dotIndex);
+        }
+        // 生成随机 UUID 作为文件名
+        String uuid = UUID.randomUUID().toString();
+        return uuid + extension;
+    }
+
+    /**
+     * 上传图片到 MinIO
+     * @param file 上传的图片文件
+     * @return 图片的访问 URL
+     * @throws IOException 输入输出异常
+     * @throws ServerException 服务器异常
+     * @throws InsufficientDataException 数据不足异常
+     * @throws ErrorResponseException 错误响应异常
+     * @throws NoSuchAlgorithmException 无此算法异常
+     * @throws InvalidKeyException 无效密钥异常
+     * @throws InvalidResponseException 无效响应异常
+     * @throws XmlParserException XML 解析异常
+     * @throws InternalException 内部异常
+     */
+    public String uploadImage(MultipartFile file) throws IOException, ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException, InvalidBucketNameException, RegionConflictException {
+        MinioClient client = getMinioClient();
+        // 检查存储桶是否存在
+        if (!client.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
+            client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
+        }
+        // 生成随机文件名
+        String objectName = generateRandomFileName(file.getOriginalFilename());
+        // 上传文件
+        client.putObject(PutObjectArgs.builder()
+                .bucket(bucketName)
+                .object(objectName)
+                .stream(file.getInputStream(), file.getSize(), -1)
+                .contentType(file.getContentType())
+                .build());
+        // 返回文件的访问 URL
+        return endpoint + "/" + bucketName + "/" + objectName;
+    }
+}

+ 288 - 0
src/main/java/com/example/course/utils/RedisClient.java

@@ -0,0 +1,288 @@
+package com.example.course.utils;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+@Component
+public class RedisClient {
+
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+
+    /**
+     * 指定缓存失效时间
+     * @param key 键
+     * @param time 时间(秒)
+     * @return
+     */
+    public boolean expire(String key,long time){
+        try {
+            if(time>0){
+                redisTemplate.expire(key, time, TimeUnit.SECONDS);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 根据key 获取过期时间
+     * @param key 键 不能为null
+     * @return 时间(秒) 返回0代表为永久有效
+     */
+    public long ttl(String key){
+        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 判断key是否存在
+     * @param key 键
+     * @return true 存在 false不存在
+     */
+    public Boolean exists(String key){
+        return redisTemplate.hasKey(key);
+    }
+
+    //============================String=============================
+    /**
+     * 普通缓存获取
+     * @param key 键
+     * @return 值
+     */
+    public Object get(String key){
+        return key==null?null:redisTemplate.opsForValue().get(key);
+    }
+
+    /**
+     * 普通缓存放入
+     * @param key 键
+     * @param value 值
+     * @return true成功 false失败
+     */
+    public boolean set(String key,Object value) {
+        try {
+            redisTemplate.opsForValue().set(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 删除缓存
+     * @param key 可以传一个值 或多个
+     */
+    public Boolean del(String key){
+        return redisTemplate.delete(key);
+    }
+
+    /**
+     * 递增
+     * @param key 键
+     * @param delta 要增加几(大于0)
+     * @return
+     */
+    public long incr(String key, long delta){
+        if(delta<0){
+            throw new RuntimeException("递增因子必须大于0");
+        }
+        return redisTemplate.opsForValue().increment(key, delta);
+    }
+
+    /**
+     * 递减
+     * @param key 键
+     * @param delta 要减少几(小于0)
+     * @return
+     */
+    public long decr(String key, long delta){
+        if(delta<0){
+            throw new RuntimeException("递减因子必须大于0");
+        }
+        return redisTemplate.opsForValue().decrement(key, -delta);
+    }
+
+    //================================hash=================================
+    /**
+     * HashGet
+     * @param key 键 不能为null
+     * @param item 项 不能为null
+     * @return 值
+     */
+    public Object hget(String key,String item){
+        return redisTemplate.opsForHash().get(key, item);
+    }
+
+    /**
+     * 向一张hash表中放入数据,如果不存在将创建
+     * @param key 键
+     * @param item 项
+     * @param value 值
+     * @return true 成功 false失败
+     */
+    public boolean hset(String key,String item,Object value) {
+        try {
+            redisTemplate.opsForHash().put(key, item, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 删除hash表中的值
+     * @param key 键 不能为null
+     * @param item 项 可以使多个 不能为null
+     */
+    public void hdel(String key, Object... item){
+        redisTemplate.opsForHash().delete(key,item);
+    }
+
+    //============================set=============================
+    /**
+     * 根据key获取Set中的所有值
+     * @param key 键
+     * @return
+     */
+    public Set<Object> smembers(String key){
+        try {
+            return redisTemplate.opsForSet().members(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 将数据放入set缓存
+     * @param key 键
+     * @param values 值 可以是多个
+     * @return 成功个数
+     */
+    public long sadd(String key, Object...values) {
+        try {
+            return redisTemplate.opsForSet().add(key, values);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+
+    /**
+     * 移除值为value的
+     * @param key 键
+     * @param values 值 可以是多个
+     * @return 移除的个数
+     */
+    public long srem(String key, Object ...values) {
+        try {
+            Long count = redisTemplate.opsForSet().remove(key, values);
+            return count;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 求差集
+     * @param key1
+     * @param key2
+     * @return
+     */
+    public Set<Object> diff(String key1, String key2) {
+        try {
+            return redisTemplate.opsForSet().difference(key1, key2);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+    //===============================list=================================
+
+    /**
+     * 获取list缓存的内容
+     * @param key 键
+     * @param start 开始
+     * @param end 结束  0 到 -1代表所有值
+     * @return
+     */
+    public List<Object> lrange(String key, long start, long end){
+        try {
+            return redisTemplate.opsForList().range(key, start, end);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     * @param key 键
+     * @param value 值
+     * @return
+     */
+    public boolean rpush(String key, Object value) {
+        try {
+            redisTemplate.opsForList().rightPush(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     * @param key 键
+     * @param value 值
+     * @return
+     */
+    public boolean lpush(String key, List<Object> value) {
+        try {
+            redisTemplate.opsForList().rightPushAll(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    public Set<Object> sdiff(String keya,String keyb){
+        try {
+            return   redisTemplate.opsForSet().difference(keya,keyb);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+
+    }
+
+    /**
+     * 移除N个值为value
+     * @param key 键
+     * @param count 移除多少个
+     * @param value 值
+     * @return 移除的个数
+     */
+    public long lrem(String key,long count,Object value) {
+        try {
+            Long remove = redisTemplate.opsForList().remove(key, count, value);
+            return remove;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+}

+ 163 - 0
src/main/java/com/example/course/utils/ResponseResult.java

@@ -0,0 +1,163 @@
+package com.example.course.utils;
+
+import java.io.Serializable;
+
+/**
+ * 通用的结果返回类
+ * @param <T>
+ */
+public class ResponseResult<T> implements Serializable {
+
+    private String host;
+
+    private Integer code;
+
+    private String errorMessage;
+
+    private T data;
+
+    public ResponseResult() {
+        this.code = 200;
+    }
+
+    public ResponseResult(Integer code, T data) {
+        this.code = code;
+        this.data = data;
+    }
+
+    public ResponseResult(Integer code, String msg, T data) {
+        this.code = code;
+        this.errorMessage = msg;
+        this.data = data;
+    }
+
+    public ResponseResult(Integer code, String msg) {
+        this.code = code;
+        this.errorMessage = msg;
+    }
+
+    public static ResponseResult errorResult(int code, String msg) {
+        ResponseResult result = new ResponseResult();
+        return result.error(code, msg);
+    }
+
+    public static ResponseResult okResult(int code, String msg) {
+        ResponseResult result = new ResponseResult();
+        return result.ok(code, null, msg);
+    }
+
+    public static ResponseResult okResult(Object data) {
+        ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getErrorMessage());
+        if(data!=null) {
+            result.setData(data);
+        }
+        return result;
+    }
+
+    public static ResponseResult errorResult(AppHttpCodeEnum enums){
+        return setAppHttpCodeEnum(enums,enums.getErrorMessage());
+    }
+
+    public static ResponseResult errorResult(AppHttpCodeEnum enums, String errorMessage){
+        return setAppHttpCodeEnum(enums,errorMessage);
+    }
+
+    public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){
+        return okResult(enums.getCode(),enums.getErrorMessage());
+    }
+
+    private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String errorMessage){
+        return okResult(enums.getCode(),errorMessage);
+    }
+
+    public ResponseResult<?> error(Integer code, String msg) {
+        this.code = code;
+        this.errorMessage = msg;
+        return this;
+    }
+
+    public ResponseResult<?> ok(Integer code, T data) {
+        this.code = code;
+        this.data = data;
+        return this;
+    }
+
+    public ResponseResult<?> ok(Integer code, T data, String msg) {
+        this.code = code;
+        this.data = data;
+        this.errorMessage = msg;
+        return this;
+    }
+
+    public ResponseResult<?> ok(T data) {
+        this.data = data;
+        return this;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public void setCode(Integer code) {
+        this.code = code;
+    }
+
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+    public void setErrorMessage(String errorMessage) {
+        this.errorMessage = errorMessage;
+    }
+
+    public T getData() {
+        return data;
+    }
+
+    public void setData(T data) {
+        this.data = data;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+
+    public static void main(String[] args) {
+        //前置
+        /*AppHttpCodeEnum success = AppHttpCodeEnum.SUCCESS;
+        System.out.println(success.getCode());
+        System.out.println(success.getErrorMessage());*/
+
+        //查询一个对象
+        /*Map map = new HashMap();
+        map.put("name","zhangsan");
+        map.put("age",18);
+        ResponseResult result = ResponseResult.okResult(map);
+        System.out.println(JSON.toJSONString(result));*/
+
+
+        //新增,修改,删除  在项目中统一返回成功即可
+       /* ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.SUCCESS);
+        System.out.println(JSON.toJSONString(result));*/
+
+
+        //根据不用的业务返回不同的提示信息  比如:当前操作需要登录、参数错误
+        /*ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
+        System.out.println(JSON.toJSONString(result));*/
+
+        //查询分页信息
+//        PageResponseResult responseResult = new PageResponseResult(1,5,50);
+//        List list = new ArrayList();
+//        list.add("itcast");
+//        list.add("it");
+//        responseResult.setData(list);
+//        System.out.println(JSON.toJSONString(responseResult));
+
+    }
+
+}

+ 44 - 0
src/main/java/com/example/course/utils/Result.java

@@ -0,0 +1,44 @@
+package com.example.course.utils;
+
+import java.io.Serializable;
+
+/**
+ * 封装返回结果
+ */
+public class Result implements Serializable{
+    private boolean flag;//执行结果,true为执行成功 false为执行失败
+    private String message;//返回结果信息
+    private Object data;//返回数据
+    public Result(boolean flag, String message) {
+        super();
+        this.flag = flag;
+        this.message = message;
+    }
+
+    public Result(boolean flag, String message, Object data) {
+        this.flag = flag;
+        this.message = message;
+        this.data = data;
+    }
+
+    public boolean isFlag() {
+        return flag;
+    }
+    public void setFlag(boolean flag) {
+        this.flag = flag;
+    }
+    public String getMessage() {
+        return message;
+    }
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public Object getData() {
+        return data;
+    }
+
+    public void setData(Object data) {
+        this.data = data;
+    }
+}

+ 136 - 0
src/main/java/com/example/course/utils/SnowflakeIdGenerator.java

@@ -0,0 +1,136 @@
+package com.example.course.utils;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+
+public class SnowflakeIdGenerator {
+    // 基础配置(Java 8+)
+    private static final long EPOCH = 1288834974657L; // 2010-11-04 00:00:00 UTC
+    private static final int DATACENTER_ID_BITS = 5;
+    private static final int WORKER_ID_BITS = 5;
+    private static final int SEQUENCE_BITS = 12;
+    // 位运算掩码
+    private static final long DATACENTER_ID_MASK = (1L << DATACENTER_ID_BITS) - 1;
+    private static final long WORKER_ID_MASK = (1L << WORKER_ID_BITS) - 1;
+    private static final long SEQUENCE_MASK = (1L << SEQUENCE_BITS) - 1;
+
+    // 位移偏移量
+    private static final int WORKER_ID_SHIFT = SEQUENCE_BITS;
+    private static final int DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
+    private static final int TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;
+
+    private final long datacenterId;
+    private final long workerId;
+    private long sequence = 0L;
+    private long lastTimestamp = -1L;
+
+    private final Object lock = new Object();
+
+    public SnowflakeIdGenerator(long datacenterId, long workerId) {
+        if ((datacenterId & DATACENTER_ID_MASK) != datacenterId) {
+            throw new IllegalArgumentException("DataCenter ID 必须在 0-31 之间");
+        }
+        if ((workerId & WORKER_ID_MASK) != workerId) {
+            throw new IllegalArgumentException("Worker ID 必须在 0-31 之间");
+        }
+        this.datacenterId = datacenterId;
+        this.workerId = workerId;
+    }
+
+    public synchronized long nextId() {
+        long currentTimestamp = timeGen();
+
+        if (currentTimestamp < lastTimestamp) {
+            throw new IllegalStateException(
+                    String.format("时钟回退 %d 毫秒,禁止生成ID(上次: %d,当前: %d)",
+                            lastTimestamp - currentTimestamp, lastTimestamp, currentTimestamp));
+        }
+
+        if (currentTimestamp == lastTimestamp) {
+            sequence = (sequence + 1) & SEQUENCE_MASK;
+            if (sequence == 0) {
+                currentTimestamp = waitNextMillis(lastTimestamp);
+            }
+        } else {
+            sequence = 0;
+        }
+
+        lastTimestamp = currentTimestamp;
+
+        return ((currentTimestamp - EPOCH) << TIMESTAMP_SHIFT) |
+                (datacenterId << DATACENTER_ID_SHIFT) |
+                (workerId << WORKER_ID_SHIFT) |
+                sequence;
+    }
+
+    private long waitNextMillis(long lastTimestamp) {
+        long timestamp = timeGen();
+        while (timestamp <= lastTimestamp) {
+            timestamp = System.currentTimeMillis(); // 改用更可靠的时间源
+        }
+        return timestamp;
+    }
+
+    private long timeGen() {
+        return System.currentTimeMillis();
+    }
+
+    // 添加 parseId 方法
+    public static SnowflakeMeta parseId(long snowflakeId) {
+        long sequence = snowflakeId & SEQUENCE_MASK;
+        long workerId = (snowflakeId >> WORKER_ID_SHIFT) & WORKER_ID_MASK;
+        long datacenterId = (snowflakeId >> DATACENTER_ID_SHIFT) & DATACENTER_ID_MASK;
+        long timestamp = (snowflakeId >> TIMESTAMP_SHIFT) + EPOCH;
+
+        return new SnowflakeMeta(
+                timestamp,
+                datacenterId,
+                workerId,
+                sequence,
+                LocalDateTime.ofEpochSecond(timestamp / 1000, 0, ZoneOffset.ofHours(8))
+        );
+    }
+    public static Long getSnowId(){
+        SnowflakeIdGenerator generator=new SnowflakeIdGenerator(1,1);
+        Long id=generator.nextId();
+        SnowflakeMeta meta=SnowflakeIdGenerator.parseId(id);
+        return id;
+    }
+    // ---------------- 修复:添加完整的getter方法 ----------------
+    public static class SnowflakeMeta {
+        private final long timestamp;
+        private final long datacenterId;
+        private final long workerId;
+        private final long sequence;
+        private final LocalDateTime dateTime;
+
+        public SnowflakeMeta(long timestamp, long datacenterId, long workerId, long sequence, LocalDateTime dateTime) {
+            this.timestamp = timestamp;
+            this.datacenterId = datacenterId;
+            this.workerId = workerId;
+            this.sequence = sequence;
+            this.dateTime = dateTime;
+        }
+
+        // 显式定义getter(修复爆红)
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        public long getDatacenterId() {
+            return datacenterId;
+        }
+
+        public long getWorkerId() {
+            return workerId;
+        }
+
+        public long getSequence() {
+            return sequence;
+        }
+
+        public LocalDateTime getDateTime() {
+            return dateTime;
+        }
+    }
+}

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

@@ -0,0 +1,24 @@
+server:
+  port: 8080
+spring:
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://47.111.97.7:3306/IM?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8
+    username: root
+    password: root
+mybatis-plus:
+  type-aliases-package: com.example.course
+  configuration:
+    map-underscore-to-camel-case: true
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  mapper-locations: classpath:mapper/*.xml
+minio:
+  endpoint: http://47.111.97.7:19000
+  accessKey: minioadmin
+  accessSecret: minioadmin
+  bucketName: test
+logging:
+  level:
+    com:
+      example:
+        course: debug

+ 34 - 0
src/main/resources/mapper/UserMapper.xml

@@ -0,0 +1,34 @@
+<?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.example.course.user.mapper.UserMapper">
+
+    <resultMap id="BaseResultMap" type="com.example.course.user.pojo.User">
+            <id property="id" column="id" jdbcType="BIGINT"/>
+            <result property="sale" column="sale" jdbcType="VARCHAR"/>
+            <result property="username" column="username" jdbcType="VARCHAR"/>
+            <result property="password" column="password" jdbcType="VARCHAR"/>
+            <result property="phone" column="phone" jdbcType="VARCHAR"/>
+            <result property="email" column="email" jdbcType="VARCHAR"/>
+            <result property="joinType" column="join_type" jdbcType="TINYINT"/>
+            <result property="avatar" column="avatar" jdbcType="VARCHAR"/>
+            <result property="signature" column="signature" jdbcType="VARCHAR"/>
+            <result property="gender" column="gender" jdbcType="TINYINT"/>
+            <result property="birthday" column="birthday" jdbcType="DATE"/>
+            <result property="status" column="status" jdbcType="TINYINT"/>
+            <result property="lastLoginTime" column="last_login_time" jdbcType="TIMESTAMP"/>
+            <result property="lastExitTime" column="last_exit_time" jdbcType="TIMESTAMP"/>
+            <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
+            <result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,sale,username,
+        password,phone,email,
+        join_type,avatar,signature,
+        gender,birthday,status,
+        last_login_time,last_exit_time,create_time,
+        update_time
+    </sql>
+</mapper>

+ 26 - 0
src/main/resources/mapper/UserSettingMapper.xml

@@ -0,0 +1,26 @@
+<?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.example.course.user.mapper.UserSettingMapper">
+
+    <resultMap id="BaseResultMap" type="com.example.course.user.pojo.UserSetting">
+            <id property="id" column="id" jdbcType="BIGINT"/>
+            <result property="userId" column="user_id" jdbcType="BIGINT"/>
+            <result property="notificationEnabled" column="notification_enabled" jdbcType="TINYINT"/>
+            <result property="soundEnabled" column="sound_enabled" jdbcType="TINYINT"/>
+            <result property="vibrateEnabled" column="vibrate_enabled" jdbcType="TINYINT"/>
+            <result property="messagePreviewEnabled" column="message_preview_enabled" jdbcType="TINYINT"/>
+            <result property="autoDownloadMedia" column="auto_download_media" jdbcType="TINYINT"/>
+            <result property="theme" column="theme" jdbcType="VARCHAR"/>
+            <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
+            <result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,user_id,notification_enabled,
+        sound_enabled,vibrate_enabled,message_preview_enabled,
+        auto_download_media,theme,create_time,
+        update_time
+    </sql>
+</mapper>

+ 13 - 0
src/test/java/com/example/course/ImApplicationTests.java

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

+ 28 - 0
src/test/java/com/example/course/Sockerservice.java

@@ -0,0 +1,28 @@
+package com.example.course;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+/**
+ * 客户端测试
+ */
+public class Sockerservice {
+    public static void main(String[] args) {
+        ServerSocket serverSocket=null;
+        try {
+             serverSocket = new ServerSocket(1024);
+            System.out.println("服务已启动,等待客户链接");
+            Socket socket = serverSocket.accept();
+            String ip = serverSocket.getInetAddress().getHostAddress();
+            System.out.println("客户已连接:"+ip+",端口:"+serverSocket.getLocalPort());
+            InputStream inputStream = socket.getInputStream();
+            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
+            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
+            System.out.println("收到客服端信息->"+bufferedReader);
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+    }
+}