LOADING

加载过慢请开启缓存 浏览器默认开启

SpringBoot 5

SpringBoot

网盘

SpringBoot 4

Spring Boot 中文文档 (springdoc.cn)

1 接口文档

2 环境搭建

  • 执行资料中的big_event.sql脚本,准备数据库表。
-- 创建数据库
create database big_event;

-- 使用数据库
use big_event;

-- 用户表
create table user (
                      id int unsigned primary key auto_increment comment 'ID',
                      username varchar(20) not null unique comment '用户名',
                      password varchar(32)  comment '密码',
                      nickname varchar(10)  default '' comment '昵称',
                      email varchar(128) default '' comment '邮箱',
                      user_pic varchar(128) default '' comment '头像',
                      create_time datetime not null comment '创建时间',
                      update_time datetime not null comment '修改时间'
) comment '用户表';

-- 分类表
create table category(
                         id int unsigned primary key auto_increment comment 'ID',
                         category_name varchar(32) not null comment '分类名称',
                         category_alias varchar(32) not null comment '分类别名',
                         create_user int unsigned not null comment '创建人ID',
                         create_time datetime not null comment '创建时间',
                         update_time datetime not null comment '修改时间',
                         constraint fk_category_user foreign key (create_user) references user(id) -- 外键约束
);

......
  • 创建springboot工程,引入对应的依赖(web、mybatis、mysql驱动)

image-20240723165057604

  • 配置文件application.yml中引入mybatis的配置信息
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/big_event
    username: root
    password: 123456
  data:
    redis:
      host: localhost
      port: 6379

mybatis:
  configuration:
    map-underscore-to-camel-case: true #开启驼峰命名和下划线命名的自动转换
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
server:
  port: 8080
  • 创建包结构,并准备实体类

3 用户相关接口实现

// UserController
@RestController
@RequestMapping("/user")
@Validated
public class UserController {

    @Autowired
    private UserService userService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    ......

}

// UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    ......
}

// UserMapper
@Mapper
public interface UserMapper {
    ......
}

// pojo.User
//lombok  在编译阶段,为实体类自动生成setter  getter toString
// pom 文件中引入依赖   在实体类上添加注解
@Data
public class User {
    @NotNull
    private Integer id;//主键ID
    private String username;//用户名
    @JsonIgnore//让springmvc把当前对象转换成json字符串的时候,忽略password,最终的json字符串中就没有password这个属性了
    private String password;//密码


    @NotEmpty
    @Pattern(regexp = "^\\S{1,10}$")
    private String nickname;//昵称

    @NotEmpty
    @Email
    private String email;//邮箱
    private String userPic;//用户头像地址
    private LocalDateTime createTime;//创建时间
    private LocalDateTime updateTime;//更新时间
}

lombok

在Spring Boot中,Lombok是一个非常方便的工具,可以通过简单的注解来简化Java开发中的样板代码,例如getter、setter、构造函数等。通过引入Lombok依赖,可以避免编写大量重复的样板代码,提高代码的可读性和可维护性。

要在Spring Boot项目中使用Lombok,首先需要在项目的pom.xml文件中添加Lombok的依赖:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version>
    <scope>provided</scope>
</dependency>

接下来,在Java类中使用Lombok注解来简化代码。以下是几个常用的Lombok注解示例:

  1. @Data:在类上添加@Data注解,会自动生成getter、setter、equals、hashCode和toString等方法。
import lombok.Data;

@Data
public class User {
    private Long id;
    private String name;
}
  1. @NoArgsConstructor:生成无参构造函数。
import lombok.NoArgsConstructor;

@NoArgsConstructor
public class User {
    private Long id;
    private String name;
}
  1. @AllArgsConstructor:生成全参构造函数。
import lombok.AllArgsConstructor;

@AllArgsConstructor
public class User {
    private Long id;
    private String name;
}
  1. @Slf4j:简化日志打印。
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MyService {
    public void doSomething() {
        log.info("Doing something...");
    }
}

通过使用这些Lombok注解,可以减少Java类中样板代码的编写,提高开发效率。需要注意的是,在使用Lombok的注解时,IDE中需要安装Lombok插件才能正常显示生成的方法。安装Lombok插件后,就可以像普通类一样调用生成的方法,而不用编写这些常见的代码了。

3.1 注册

// UserController
    @PostMapping("/register")
    public Result register(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password) {

        //查询用户
        User u = userService.findByUserName(username);
        if (u == null) {
            //没有占用
            //注册
            userService.register(username, password);
            return Result.success();
        } else {
            //占用
            return Result.error("用户名已被占用");
        }
    }

// UserServiceImpl
    @Override
    public User findByUserName(String username) {
        User u = userMapper.findByUserName(username);
        return u;
    }

    @Override
    public void register(String username, String password) {
        //加密
        String md5String = Md5Util.getMD5String(password);
        //添加
        userMapper.add(username,md5String);
    }

// UserMapper
    //根据用户名查询用户
    @Select("select * from user where username=#{username}")
    User findByUserName(String username);

    //添加
    @Insert("insert into user(username,password,create_time,update_time)" +
            " values(#{username},#{password},now(),now())")
    void add(String username, String password);


//`UserMapper`使用了`MyBatis`的注解来映射数据库操作:
//
//        1. `@Select("select * from user where username=#{username}")`:
//        - 这是`MyBatis`中的`@Select`注解,用于执行查询操作,括号中的内容为SQL查询语句。
//        - `select * from user where username=#{username}`表示在数据库表`User`中根据用户名查询用户信息,其中`#{username}`是一个占位符,会在SQL执行时被具体的参数替代。
//
//        2. `@Insert("insert into user(username,password,create_time,update_time) values(#{username},#{password},now(),now())")`:
//        - 这是`MyBatis`中的`@Insert`注解,用于执行插入操作,括号中的内容为SQL插入语句。
//        - `insert into user(username,password,create_time,update_time) values(#{username},#{password},now(),now())`表示向数据库表"User"中插入用户信息,包括用户名、加密后的密码以及创建时间和更新时间。
//        - `#{username}`和`#{password}`是占位符,会在SQL执行时被具体的参数替代。
//        - `now()`表示当前时间,用于设置创建时间和更新时间为当前操作的时间。
  1. UserController:
    • @PostMapping("/register"): 这是一个用于处理POST请求的方法,路径为/register
    • public Result register(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password): 这个方法接收两个参数,用户名和密码,分别使用正则表达式验证了输入的内容格式。
    • 首先通过userService.findByUserName(username)查询数据库中是否已存在相同用户名的用户。
    • 如果用户不存在,则调用userService.register(username, password)方法进行用户注册,将明文密码加密后保存到数据库,并返回成功的结果。
    • 如果用户已存在,则返回一个包含错误信息的Result对象。
  2. UserServiceImpl:
    • findByUserName(String username): 根据用户名查询用户,调用userMapper.findByUserName(username)查询数据库并返回结果。
    • register(String username, String password): 注册用户,对密码进行MD5加密后,调用userMapper.add(username, md5String)将用户信息插入数据库。
  3. UserMapper:
    • findByUserName(String username): 根据用户名查询用户,使用SQL语句查询数据库中的用户信息。
    • add(String username, String password): 添加用户,使用SQL语句将用户名、加密后的密码以及创建时间和更新时间插入到数据库中。

image-20240723171744966

3.2 登录

// UserController
    @PostMapping("/login")
    public Result<String> login(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password) {
        //根据用户名查询用户
        User loginUser = userService.findByUserName(username);
        //判断该用户是否存在
        if (loginUser == null) {
            return Result.error("用户名错误");
        }

        //判断密码是否正确  loginUser对象中的password是密文
        if (Md5Util.getMD5String(password).equals(loginUser.getPassword())) {
            //登录成功
            Map<String, Object> claims = new HashMap<>();
            claims.put("id", loginUser.getId());
            claims.put("username", loginUser.getUsername());
            String token = JwtUtil.genToken(claims);
            //把token存储到redis中
            ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
            operations.set(token,token,1, TimeUnit.HOURS);
            return Result.success(token);
        }
        return Result.error("密码错误");
    }
//

3.3 获取用户详细信息

// UserController
    @GetMapping("/userInfo")
    public Result<User> userInfo(/*@RequestHeader(name = "Authorization") String token*/) {
        //根据用户名查询用户
        Map<String, Object> map = ThreadLocalUtil.get();
        String username = (String) map.get("username");
        User user = userService.findByUserName(username);
        return Result.success(user);
    }

3.4 更新用户密码

// UserController
    @PatchMapping("/updatePwd")
    public Result updatePwd(@RequestBody Map<String, String> params,@RequestHeader("Authorization") String token) {
        //1.校验参数
        String oldPwd = params.get("old_pwd");
        String newPwd = params.get("new_pwd");
        String rePwd = params.get("re_pwd");

        if (!StringUtils.hasLength(oldPwd) || !StringUtils.hasLength(newPwd) || !StringUtils.hasLength(rePwd)) {
            return Result.error("缺少必要的参数");
        }

        //原密码是否正确
        //调用userService根据用户名拿到原密码,再和old_pwd比对
        Map<String,Object> map = ThreadLocalUtil.get();
        String username = (String) map.get("username");
        User loginUser = userService.findByUserName(username);
        if (!loginUser.getPassword().equals(Md5Util.getMD5String(oldPwd))){
            return Result.error("原密码填写不正确");
        }

        //newPwd和rePwd是否一样
        if (!rePwd.equals(newPwd)){
            return Result.error("两次填写的新密码不一样");
        }

        //2.调用service完成密码更新
        userService.updatePwd(newPwd);
        //删除redis中对应的token
        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
        operations.getOperations().delete(token);
        return Result.success();
    }

// UserServiceImpl
    @Override
    public void updatePwd(String newPwd) {
        Map<String,Object> map = ThreadLocalUtil.get();
        Integer id = (Integer) map.get("id");
        userMapper.updatePwd(Md5Util.getMD5String(newPwd),id);
    }

// UserMapper
  @Update("update user set password=#{md5String},update_time=now() where id=#{id}")
    void updatePwd(String md5String, Integer id);

这段代码展示了一个用户密码更新的过程,包括控制器(UserController)、服务实现类(UserServiceImpl)和数据访问层(UserMapper):

  1. UserController:

    • @PatchMapping("/updatePwd") 注解表示该方法用于处理 PATCH 请求,路径为 “/updatePwd”。
    • updatePwd 方法接收一个包含参数和请求头的 Map 对象和一个名为 “Authorization” 的请求头,用于更新用户密码。
    • 首先校验参数,包括旧密码、新密码和确认密码是否存在。
    • ThreadLocalUtil 中获取用户信息(在拦截器中存储的业务数据),获取用户名,然后通过用户名查询数据库获取用户信息。
    • 检查旧密码是否正确,如果不正确则返回错误信息。
    • 检查新密码和确认密码是否一致,如果不一致则返回错误信息。
    • 调用 userService.updatePwd(newPwd) 方法更新用户密码,然后从 Redis 中删除对应的令牌,最后返回成功的结果。
  2. UserServiceImpl:

    • updatePwd 方法用于更新用户密码,接收一个新密码参数。
    • ThreadLocalUtil 中获取用户信息,获取用户 ID。
    • 调用 userMapper.updatePwd(Md5Util.getMD5String(newPwd), id) 方法更新用户密码,使用 MD5 加密存储密码。
  3. UserMapper:

    • @Update("update user set password=#{md5String},update_time=now() where id=#{id}") 是 MyBatis 的注解,表示执行更新用户密码的 SQL 语句。
    • updatePwd 方法用于更新用户密码,接收一个经过 MD5 加密的新密码和用户 ID,更新用户密码和更新时间。

4 工具类介绍

4.1 Result

package com.itheima.pojo;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

//统一响应结果
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Result<T> {
    private Integer code;//业务状态码  0-成功  1-失败
    private String message;//提示信息
    private T data;//响应数据

    //快速返回操作成功响应结果(带响应数据)
    public static <E> Result<E> success(E data) {
        return new Result<>(0, "操作成功", data);
    }

    //快速返回操作成功响应结果
    public static Result success() {
        return new Result(0, "操作成功", null);
    }

    public static Result error(String message) {
        return new Result(1, message, null);
    }
}

Spring Boot中给post请求返回json可以通过@RestController注解来实现。这个注解表示这个类中的方法会默认返回json格式的数据。

4.2 Md5Util

public class Md5Util {
    /**
     * 默认的密码字符串组合,用来将字节转换成 16 进制表示的字符,apache校验下载的文件的正确性用的就是默认的这个组合
     */
    protected static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    protected static MessageDigest messagedigest = null;

    static {
        try {
            messagedigest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException nsaex) {
            System.err.println(Md5Util.class.getName() + "初始化失败,MessageDigest不支持MD5Util。");
            nsaex.printStackTrace();
        }
    }

    /**
     * 生成字符串的md5校验值
     *
     * @param s
     * @return
     */
    public static String getMD5String(String s) {
        return getMD5String(s.getBytes());
    }

    /**
     * 判断字符串的md5校验码是否与一个已知的md5码相匹配
     *
     * @param password  要校验的字符串
     * @param md5PwdStr 已知的md5校验码
     * @return
     */
    public static boolean checkPassword(String password, String md5PwdStr) {
        String s = getMD5String(password);
        return s.equals(md5PwdStr);
    }


    public static String getMD5String(byte[] bytes) {
        messagedigest.update(bytes);
        return bufferToHex(messagedigest.digest());
    }

    private static String bufferToHex(byte bytes[]) {
        return bufferToHex(bytes, 0, bytes.length);
    }

    private static String bufferToHex(byte bytes[], int m, int n) {
        StringBuffer stringbuffer = new StringBuffer(2 * n);
        int k = m + n;
        for (int l = m; l < k; l++) {
            appendHexPair(bytes[l], stringbuffer);
        }
        return stringbuffer.toString();
    }

    private static void appendHexPair(byte bt, StringBuffer stringbuffer) {
        char c0 = hexDigits[(bt & 0xf0) >> 4];// 取字节中高 4 位的数字转换, >>>
        // 为逻辑右移,将符号位一起右移,此处未发现两种符号有何不同
        char c1 = hexDigits[bt & 0xf];// 取字节中低 4 位的数字转换
        stringbuffer.append(c0);
        stringbuffer.append(c1);
    }

}

4.3 JwtUtil


public class JwtUtil {

    private static final String KEY = "itheima";

    //接收业务数据,生成token并返回
    public static String genToken(Map<String, Object> claims) {
        return JWT.create()
                .withClaim("claims", claims)
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 ))
                .sign(Algorithm.HMAC256(KEY));
    }

    //接收token,验证token,并返回业务数据
    public static Map<String, Object> parseToken(String token) {
        return JWT.require(Algorithm.HMAC256(KEY))
                .build()
                .verify(token)
                .getClaim("claims")
                .asMap();
    }

}

4.4 ThreadLocalUtil

@SuppressWarnings("all")
public class ThreadLocalUtil {
    //提供ThreadLocal对象,
    private static final ThreadLocal THREAD_LOCAL = new ThreadLocal();

    //根据键获取值
    public static <T> T get(){
        return (T) THREAD_LOCAL.get();
    }

    //存储键值对
    public static void set(Object value){
        THREAD_LOCAL.set(value);
    }


    //清除ThreadLocal 防止内存泄漏
    public static void remove(){
        THREAD_LOCAL.remove();
    }
}

4.5 LoginInterceptor

interceptors.LoginInterceptor

@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //令牌验证
        String token = request.getHeader("Authorization");
        //验证token
        try {
            //从redis中获取相同的token
            ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
            String redisToken = operations.get(token);
            if (redisToken==null){
                //token已经失效了
                throw new RuntimeException();
            }
            Map<String, Object> claims = JwtUtil.parseToken(token);

            //把业务数据存储到ThreadLocal中
            ThreadLocalUtil.set(claims);
            //放行
            return true;
        } catch (Exception e) {
            //http响应状态码为401
            response.setStatus(401);
            //不放行
            return false;
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //清空ThreadLocal中的数据
        ThreadLocalUtil.remove();
    }
}

cofig.webConfig

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //登录接口和注册接口不拦截
        registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login","/user/register");
    }
}

代码展示了一个基于Spring框架的拦截器实现:

  1. LoginInterceptor 类实现了Spring框架的 HandlerInterceptor 接口,用于拦截请求并进行登录验证操作。
  2. LoginInterceptor 中,preHandle 方法用于在请求处理之前执行验证操作。它首先获取请求中的名为 “Authorization“ 的头部信息作为令牌。
  3. 然后,它通过注入的 StringRedisTemplate 来访问 Redis 存储,检查该令牌是否存在于 Redis 中。如果 Redis 中不存在该令牌,说明令牌已失效,则抛出异常并返回 HTTP 状态码 401。
  4. 如果令牌有效,它调用 JwtUtil.parseToken(token) 方法来解析令牌,并将业务数据存储到 ThreadLocal 中,以便后续请求处理使用。
  5. afterCompletion 方法用于清除 ThreadLocal 中的数据,以确保不发生内存泄漏问题。

  6. WebConfig 类实现了 WebMvcConfigurer 接口,用于配置拦截器。在 addInterceptors 方法中,将 LoginInterceptor 添加到拦截器注册表中,并排除 /user/login/user/register 接口,即登录和注册接口不会被该拦截器拦截。

代码的作用是实现了一个基于令牌验证的登录拦截器,用于保护特定的接口不被未认证的用户访问。

5 文件上传

FileUploadController

@RestController
public class FileUploadController {

    @PostMapping("/upload")
    public Result<String> upload(MultipartFile file) throws Exception {
        //把文件的内容存储到本地磁盘上
        String originalFilename = file.getOriginalFilename();
        //保证文件的名字是唯一的,从而防止文件覆盖
        String filename = UUID.randomUUID().toString()+originalFilename.substring(originalFilename.lastIndexOf("."));
        String url = AliOssUtil.uploadFile(filename,file.getInputStream());
        return Result.success(url);
    }
}

AliOssUtil

public class AliOssUtil {

    // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
    private static final String ENDPOINT = "https://oss-cn-beijing.aliyuncs.com";
    // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
    //EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
    private static final String ACCESS_KEY_ID="";
    private static final String ACCESS_KEY_SECRET="";
    // 填写Bucket名称,例如examplebucket。
    private static final String BUCKET_NAME = "big-event";

    public static String uploadFile(String objectName, InputStream in) throws Exception {


        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(ENDPOINT,ACCESS_KEY_ID, ACCESS_KEY_SECRET);
        String url = "";
        try {
            // 填写字符串。
            String content = "Hello OSS,你好世界";

            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, objectName, in);

            // 上传字符串。
            PutObjectResult result = ossClient.putObject(putObjectRequest);
            //url组成: https://bucket名称.区域节点/objectName
            url = "https://"+BUCKET_NAME+"."+ENDPOINT.substring(ENDPOINT.lastIndexOf("/")+1)+"/"+objectName;
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        return url;
    }
}

6 Postman测试

PostMan中文文档