SpringBoot学习

Spring缺点:

        1)配置繁琐

        2)依赖繁琐

SpringBoot:

        1)自动配置

                SpringBoot的自动配置是一个运行时(更准确的说,是应用程序启动时)的过程,考虑了众多因素,才决定Spring配置应该用哪个,不应该用哪个。该过程是SpringBoot自动完成的。

        2)起步依赖

                起步依赖本质上是一个Maven项目对象模型,定义了对其他库的传递依赖,这些东西加在一起即支持某项目功能

        3)辅助功能

                提供了一些大型项目中常见的非功能性特征,如嵌入式服务器、安全、指标、健康检查、外部配置。

*总结:SpringBoot提供了一种快速开发Spring项目的方式,而不是对Spring的功能上的增强。

SpringBootg工程搭建:

        1)maven方式创建

        2)下载插件 Alibaba Cloud Toolkit

                 下载慢可以配置代理:

JetBrains Marketplaceicon-default.png?t=N7T8https://plugins.jetbrains.com        3)Spring Initializr方式创建

SpringBoot起步依赖原理分析:

        1)spring-boot-starter-paren

//继承父工程
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.3</version>
</parent>

        2)spring-boot-starter-web

//导入起步依赖
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
</dependency>

        *总结

                1、在spring-boot-starter-parent中定义了各种技术的版本信息,组合了一套最优搭配的技术版本        

                2、在各种starter中,定义了完成该功能需要的坐标合集,其中大部分版本信息来自于父工程        

                3、我们的工程继承parent,引入starter后,通过依赖传递,就可以简单方便获取需要的jar包,并且不会存在版本冲突等问题。

SpringBoot配置

       1) 配置文件分类

                SpringBoot 是基础约定熟成的,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用application.properties或者application.yml(application.yaml)进行配置。

  • properties:
server.port=8080
  • yml:(: 后要留空格)
server: 
    port: 8080

#对象(map):键值对的集合
person: 
    name: zhangsan
person: {name: zhangsan}#行内写法

#数组:一组按次序排列的值
address: 
    - bejing
    - shanghai
address: [beijing,shanghai]#行内写法

#纯量:单个的、不可再分的值
msg1: 'hello \n world' #单引号忽略转义字符
msg2: "hello \n world" #双引号识别转义字符


#参数引用
name:zhgangsan
person: 
    name: ${name} #引用上边定义的name值

        *总结:在同一级目录下优先级为:properties>yml>yaml

        2)读取配置文件

                1)@Value

@Value("${name}")//name要和配置文件的名字一致
private String name;

@Value("${person.name}")
private String name2;

@Value("${person.age}")
private int age;

@Value("${address[0]}")
private int address1;

@Value("${address[1]}")
private int address1;

                2)Environment

@Autowired
private Emvironment env


//获取

System.out.println(env.getProperty('preson.name'));
System.out.println(env.getProperty('address[0]'));

                3)@ConfigurationProperties

@ConfigurationProperties(prefix = "person")//前缀指定那个变量
public class Person {

    private String name;

    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }


<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <optional>true</optional>
</dependency>

        3)多环境配置

                profile 可以让 Spring 对不同的环境提供不同配置的功能,可以通过激活、指定参数等方式快速切换环境。 换句话说,就是我们需要在不同的场景下使用不同的配置,profile的出现就是要解决我们多环境下切换配置复杂的问题。

                在实际开发环境中,我们存在开发环境的配置,部署环境的配置,测试环境的配置等等,里面的配置信息很多时,例如:端口、上下文路径、数据库配置等等,若每次切换环境时,我们都需要进行修改这些配置信息时,会比较麻烦,profile的出现就是为了解决这个问题。

                语法规则:application-{profile}.properties(.yaml/.yml)

                如果需要创建自定义的的properties文件时,可以用application-xxx.properties/yml的命名方式,这也是官方提供的,其中xxx可以根据自己的需求来定义。

                测试案例:创建application-dev.yml和application-prod.yml两个配置文件,指定不同的端口

        application-dev.yml
# 开发环境
jdbc:
  driverclass: com.mysql.jdbc.Driver
  url: jdbc:mysql://localhost:3306/java79?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
  username: zhangsan
  password: 123456
        application-test.yml
# 测试环境
jdbc:
  driverclass: com.mysql.jdbc.Driver
  url: jdbc:mysql://localhost:3306/java79?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
  username: test
  password: test

如果需要在两种环境下进行切换,只需要在application.yml中加入如下内容即可

spring:
  profiles:
    active: dev
        常用环境

application.properties:主配置文件 application-dev.properties:开发环境配置文件 application-test.properties:测试环境配置文件 application-prod.properties:生产环境配置文件

SpringBoot热部署:

        

        添加依赖:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-devtools</artifactId>
	<optional>true</optional>
	<scope>runtime</scope>
</dependency>

        添加热部署配置:

spring:
  #热部署配置
  devtools:
    restart:
      enabled: true #设置开启热部署
      additional-paths: src/main/java #重启目录
      exclude: WEB-INF/**
    freemarker:
      cache: false #页面不加载缓存修改及时生效

idea 添加配置 File -> settings ->Compiler 将Bulid project automatically 勾选上。

在这里插入图片描述

Ctrl + Shift + Alt + / 快捷键 弹出Maintenance窗口,选择Registry选项

在这里插入图片描述

找到key为compiler.automake.allow.when.app.running 将其勾选

在这里插入图片描述

重启项目即可

image-20220913161209773

整合Swagger3:

        1)导入依赖

<!--文档框架 springboot2 + Openapi3-->
<dependency>
     <groupId>com.github.xiaoymin</groupId>
     <artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
     <version>4.3.0</version>
</dependency>

        2)配置swagger

# 文档框架配置
springdoc:
  swagger-ui:
    path: /swagger-ui.html
    tags-sorter: alpha
    operations-sorter: alpha
    show-extensions: true
  api-docs:
    path: /v3/api-docs
  group-configs:
    - group: 'default'
      paths-to-match: '/**'
      packages-to-scan: com.xxgc.demo.controller #包扫描路径
  default-flat-param-object: false
knife4j:
  enable: true
  setting:
    language: zh_cn
    swagger-model-name: 实体类列表
  basic:
    enable: true #进入文档需要输入密码
    username: admin
    password: 123456

        3)创建SwaggerConfig.java

package com.xxgc.demo.config;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.parameters.HeaderParameter;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springdoc.core.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;


/**
 * @Author:SJY
 * @Date :2023/9/5 - 09 - 05 - 16:35
 * thanksjava@qq.com
 */
@Configuration
public class SwaggerConfig {
    /**
     * 根据@Tag 上的排序,写入x-order
     *
     * @return the global open api customizer
     */
 /*   @Bean
    public GlobalOpenApiCustomizer orderGlobalOpenApiCustomizer() {
        return openApi -> {
            if (openApi.getTags()!=null){
                openApi.getTags().forEach(tag -> {
                    Map<String,Object> map=new HashMap<>();
                    map.put("x-order", RandomUtil.randomInt(0,100));
                    tag.setExtensions(map);
                });
            }
            if(openApi.getPaths()!=null){
                openApi.addExtension("x-test123","333");
                openApi.getPaths().addExtension("x-abb",RandomUtil.randomInt(1,100));
            }

        };
    }*/

    @Bean
    public GroupedOpenApi userApi(){
        String[] paths = { "/**" };
        String[] packagedToMatch = { "com.xiaominfo.knife4j.demo.web" };
        return GroupedOpenApi.builder().group("用户模块")
                .pathsToMatch(paths)
                .addOperationCustomizer((operation, handlerMethod) -> {
                    return operation.addParametersItem(new HeaderParameter().name("groupCode").example("测试").description("集团code").schema(new StringSchema()._default("BR").name("groupCode").description("集团code")));
                })
                .packagesToScan(packagedToMatch).build();
    }
    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title("XXX用户系统API")
                        .version("1.0")

                        .description( "Knife4j集成springdoc-openapi示例")
                        .termsOfService("http://doc.xiaominfo.com")
                        .license(new License().name("Apache 2.0")
                                .url("http://doc.xiaominfo.com"))
                ).addSecurityItem(new SecurityRequirement().addList(HttpHeaders.AUTHORIZATION))
                .components(new Components().addSecuritySchemes(HttpHeaders.AUTHORIZATION,new SecurityScheme()
                        .name(HttpHeaders.AUTHORIZATION).type(SecurityScheme.Type.HTTP).scheme("bearer")));
    }


}

        4)常用注解:

@Api(tags = "类上的描述")
@ApiOperation(value = "方法的描述", notes = "接口的描述")
@ApiModel(value = "模型的描述", description = "")
@ApiModelProperty("字段的描述")
@Operation(summary = "获取信息测试",description = "测试用")



@Parameters({
            @Parameter(name = "uname",description = "用户名",required = true,example = "admin"),
            @Parameter(name = "pword",description = "密码",required = true,example = "123"),
    })

@Schema(description = "系统内部状态码")

       

 基于Lombok的多种用法:

        1)导入依赖

<!--lombok 自动生成set、get、有参、无参、toString等常用方法-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
            <scope>provided</scope>
        </dependency>

        2)下载插件

                LomBok

        3)添加注解

/**
 * 项目统一返回结果对象
 * lombok 自动生成set get方法
 * @Data set+get+tostring 方法
 * @AllArgsConstructor 全参构造方法
 * @NoArgsConstructor 无参构造方法
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {

    @Schema(description = "系统内部状态码")
    private int code;

    @Schema(description = "系统信息")
    private String msg;

    @Schema(description = "返回数据")
    private T data;

基于HuTool进行数据脱敏

        1)导入依赖


<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.16</version>
</dependency>

        2)参考文档

HutoolHutool 官方文档icon-default.png?t=N7T8https://doc.hutool.cn/

  统一参数返回

        1.封装自定义参数返回类:

package com.xgd.demo.controller.result;

import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 项目统一返回结果对象
 * lombok 自动生成set get方法
 * @Data set+get+tostring 方法
 * @AllArgsConstructor 全参构造方法
 * @NoArgsConstructor 无参构造方法
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
//如果值为空,在转json时就移除
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Result<T> {

    @Schema(description = "系统内部状态码")
    private int code;

    @Schema(description = "系统信息")
    private String msg;

    @Schema(description = "返回数据")
    private T data;

    //--------------改变部分信息-----------------
    public Result msg(String msg){
        this.msg = msg;
        return this;
    }
    public Result code(int code){
        this.code = code;
        return this;
    }

    //请求成功的返回
    public static <T> Result<T> ok(T data){
        return new Result(200,"请求成功",data);
    }
    //请求成功的返回 不带参数
    public static <T> Result<T> ok(){
        return new Result(200,"请求成功",null);
    }

    //请求失败的返回 不带参数
    public static <T> Result<T> error(){
        return new Result(500,"请求失败",null);
    }
    //请求失败的返回 异常拦截使用
    public static <T> Result<T> error(int code,T data){
        return new Result(code,"请求失败",data);
    }
}

全局异常处理

        1)创建全局异常处理类:GlobalExceptionHandler

        2)编写异常处理类型

package com.xxgc.demo.controller.error;

import com.xxgc.demo.controller.result.Result;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
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 javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Author: SJY
 * @Date :2023/9/8 - 09 - 08 - 14:37
 * 全局异常拦截器 @ControllerAdvice
 */
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

    //参数校验的拦截
    @ExceptionHandler(value = {BindException.class, ValidationException.class, MethodArgumentNotValidException.class})
    public ResponseEntity<Result> handleValidatedException(Exception e) {
        Result<List<String>> result = null;
        if (e instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e;
            List<String> errorMessages = ex.getBindingResult().getAllErrors().stream()
                    .map(ObjectError::getDefaultMessage)
                    .collect(Collectors.toList());
            result = Result.error(HttpStatus.BAD_REQUEST.value(), errorMessages);
        } else if (e instanceof ConstraintViolationException) {
            ConstraintViolationException ex = (ConstraintViolationException) e;
            List<String> errorMessages = ex.getConstraintViolations().stream()
                    .map(ConstraintViolation::getMessage)
                    .collect(Collectors.toList());
            result = Result.error(HttpStatus.BAD_REQUEST.value(), errorMessages);
        } else if (e instanceof BindException) {
            BindException ex = (BindException) e;
            List<String> errorMessages = ex.getAllErrors().stream()
                    .map(ObjectError::getDefaultMessage)
                    .collect(Collectors.toList());
            result = Result.error(HttpStatus.BAD_REQUEST.value(), errorMessages);
        }
        return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
    }

    //参数转换
    @ExceptionHandler(value = {HttpMessageNotReadableException.class})
    public ResponseEntity<Result> httpMessageNotReadableException(Exception e) {
        return new ResponseEntity<>(Result.error().msg("参数类型转换失败").errorMsg(e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
    }
    

    /**------------------------ 一定写在最下面 ----------------------------------**/
    /**------------------@TODO 开发环境和测试环境不用 只在生产环境用 留坑 (虚竹)-------------------**/
    //运行时的异常
    @ExceptionHandler(value = {Exception.class})
    public ResponseEntity<Result> runException(Exception e) {
        return new ResponseEntity<>(Result.error().msg("当前服务器压力大,请稍后重试"), HttpStatus.INTERNAL_SERVER_ERROR);
    }

}

常用参数校验注解

@Valid: 该注解用于指示一个参数或请求体需要进行验证。可以将该注解添加到参数上,表示该参数需要进行验证。
@NotNull: 该注解用于指示一个参数不能为null。如果参数值为null,则会抛出一个异常。
@NotEmpty: 该注解用于指示一个参数不能为空。如果参数值为空,则会抛出一个异常。
@Size(min=, max=): 该注解用于指示一个参数的长度必须在指定范围内。其中,min表示参数长度的最小值,max表示参数长度的最大值。
@Pattern(regex=, flags=): 该注解用于指示一个参数的值必须匹配指定的正则表达式。其中,regex表示正则表达式,flags表示正则表达式的标志。
@Min(value=): 该注解用于指示一个参数的值必须大于等于指定的最小值。
@Max(value=): 该注解用于指示一个参数的值必须小于等于指定的最大值。
@DecimalMin(value=): 该注解用于指示一个参数的值必须大于等于指定的最小十进制值。
@DecimalMax(value=): 该注解用于指示一个参数的值必须小于等于指定的最大十进制值。
@Email(message=): 该注解用于指示一个参数的值必须是一个有效的电子邮件地址。如果参数值不是有效的电子邮件地址,则会抛出一个异常,并且可以在message中指定异常消息。
@EqualTo(referringParamName=): 该注解用于指示一个参数的值必须等于另一个参数的值。其中,referringParamName表示另一个参数的名称。

自定义注解

        1)拦截器方式:

        2)切面AOP方式:

                导入依赖:
<!--SpringAOP的包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
                 
        创建一个自定义注解:

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 CheckIdCard {

}
       创建一个Aspect来处理这个注解: 
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

@Aspect
@Component
public class IdCardInterceptor {

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

    @Around("execution(* com.yourpackage..*(..)) && args(idCard)")'execution(* com.yourpackage..*(..))' 是一个方法匹配器,表示这个环绕建议应用于com.yourpackage包及其子包中所有的方法。'args(idCard)' 是一个参数匹配器,表示这个环绕建议只应用于含有idCard参数的方法
    //@Around("@annotation(CheckIdCard)")表示这个方法会在带有CheckIdCard注解的方法被调用之前执行
    public Object checkIdCard(ProceedingJoinPoint joinPoint, String idCard) throws ParseException {
        try {
            Date birthDate = sdf.parse(idCard.substring(6, 14)); // 假设身份证号的出生日期在从左起第6到14位
            Date currentDate = new Date();
            if (currentDate.after(birthDate)) {
                throw new RuntimeException("身份证号出生日期不能在当前日期之前");
            }
        } catch (ParseException e) {
            throw new RuntimeException("身份证号格式不正确", e);
        }
        return joinPoint.proceed();
    }
}

         3)巧用ConstraintValidator接口方式:

                        创建自定义注解类:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;

@Documented
@Constraint(validatedBy = {IdCardBirthdayBeforeCurrentDateValidator.class})//用于指定一个或多个验证器来验证类或字段的约束。在这个例子中,它指定了 IdNoValidator 类作为验证器来验证该类或字段的约束。具体来说,它可以用于验证身份证号码、账号、手机号码等各种类型的标识号码。具体的验证逻辑需要在 IdNoValidator 类中实现。
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IdCardBirthdayBeforeCurrentDate {
    String message() default "身份证号码的出生日期不能在当前日期之前";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    String regexp() default "^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$";
}

        在这个注解类中,我们定义了一个regexp属性,用于存储身份证号码的正则表达式。这个正则表达式可以匹配所有的有效身份证号码,包括出生日期在当前日期之前的身份证号码。


                创建一个自定义验证器类,该类需要实现ConstraintValidator接口。在这个类中,您可以实现isValid方法,用于检查身份证号码的出生日期是否在当前日期之前。
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class IdCardBirthdayBeforeCurrentDateValidator implements ConstraintValidator<IdCardBirthdayBeforeCurrentDate, String> {
    private String regexp;

    @Override
    public void initialize(IdCardBirthdayBeforeCurrentDate constraintAnnotation) {
        this.regexp = constraintAnnotation.regexp();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null || value.isEmpty()) {
            return true;
        }

        Pattern pattern = Pattern.compile(regexp);
        Matcher matcher = pattern.matcher(value);
        if (!matcher.matches()) {
            return false;
        }

          //int year = Integer.parseInt(idNo.substring(6, 10));
         //int month = Integer.parseInt(idNo.substring(10, 12));
         //int day = Integer.parseInt(idNo.substring(12, 14));

        // Calendar calendar = Calendar.getInstance();
         //calendar.set(year, month, day);
         //return calendar.getTime().before(new Date());

        String birthdayStr = value.substring(6, 14);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        LocalDate birthday = LocalDate.parse(birthdayStr, formatter);
        LocalDate currentDate = LocalDate.now();
        return birthday.isBefore(currentDate);
    }
}

        在这个验证器类中,我们实现了isValid方法,用于检查身份证号码的出生日期是否在当前日期之前。我们首先检查身份证号码是否符合正则表达式。如果不符合,则返回false。如果符合,则将出生日期转换为LocalDate类型,并与当前日期进行比较。如果出生日期在当前日期之前,则返回true,否则返回false。


                使用@IdCardBirthdayBeforeCurrentDate注解来指定身份证号码的正则表达式
import javax.validation.constraints.Pattern;

public class User {
    @Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$", message = "身份证号码的出生日期不能在当前日期之前")
    private String idCard;

    // getters and setters
}
                ConstraintValidator介绍:
                                1)ConstraintValidator是Java中的一种工具类,用于做实体类字段校验
                                2)ConstraintValidator接口是Hibernate Validator库中的一个重要组件,用于实现自定义的验证规则。这些规则可以应用于Java Bean中的字段,以进行各种复杂的数据验证。
           3)ConstraintValidator接口有两个主要方法:initializeisValid
  1. initialize方法:此方法在验证器创建时被调用,可以用于执行任何必要的初始化工作,例如,加载配置信息、初始化状态等。
  2. isValid方法:此方法用于执行实际的验证逻辑。它接收两个参数:一个是待验证的实体对象(即要进行验证的Java Bean),另一个是ConstraintValidatorContext对象,它可以提供额外的上下文信息,如字段名称、字段索引等。通过这个方法,你可以实现自定义的验证逻辑,例如检查数据是否满足特定的格式、范围、业务规则等

整合JWT

        1)导入依赖

<dependency>
     <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
      <version>3.4.0</version>
</dependency>

        2)如何使用

        JWT工具-JWTUtil | HutoolHutool 官方文档icon-default.png?t=N7T8https://doc.hutool.cn/pages/JWTUtil/#%E4%BD%BF%E7%94%A8

整合mybatis-plus

        1)添加mybatis-plus依赖

<dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>最新版本</version>
    </dependency>

        2)导入jdbc的mysql驱动依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.26</version>
</dependency>

        3)配置application.yml

 
spring:
    # 配置数据源信息 
    datasource:
        # 配置数据源类型
        type: com.zaxxer.hikari.HikariDataSource
        # 配置连接数据库信息
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
        username: root
        password: 123456
# MyBatis Plus配置
mybatis-plus:
  # 搜索指定包别名
  typeAliasesPackage: com.ruoyi.**.domain
  # 配置mapper的扫描,找到所有的mapper.xml映射文件
  mapperLocations: classpath*:mapper/**/*Mapper.xml
  configLocation: 
    classpath:mybatis/mybatis-config.xml # 加载全局的配置文件
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #是否打印sql语句

整合Redis

        1)导入依赖

<!-- redis 缓存操作 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        2) 配置application.yml

# redis 配置
  redis:
    # 地址
    host: localhost
    # 端口,默认为6379
    port: 6379
    # 数据库索引
    database: 0
    # 密码
    password:
    # 连接超时时间
    timeout: 10s
    lettuce:
      pool:
        # 连接池中的最小空闲连接
        min-idle: 0
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池的最大数据库连接数
        max-active: 8
        # #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms

        3)新建redis.config配置类,配置模板

@Configuration
public class RedisConfig {

    @Bean
    public RedisSerializer<Object> redisSerializer() {
        //创建JSON序列化器
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.configure(MapperFeature.USE_ANNOTATIONS, false);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        //解决localDateTime的序列化问题
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        //必须设置,否则无法将JSON转化为对象,会转化成Map类型
        objectMapper.disableDefaultTyping();
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        //objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(objectMapper);
        return serializer;
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
        //设置Redis缓存有效期为1天
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer())).entryTtl(Duration.ofDays(1));
        return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
    }


    @Bean
    @ConditionalOnMissingBean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisSerializer<Object> serializer = redisSerializer();
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(serializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(serializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

        3)新建RedisUtil.java,创建工具类

@Component
public class RedisUtil {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    // =============================Common 基础============================
    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     */
    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 getExpire(String key) {
        // 如果返回值为 null,则返回 0L
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }
    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return Boolean.TRUE.equals(redisTemplate.hasKey(key));
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(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   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time,
                        TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 递增
     *
     * @param key   键
     * @param delta 要增加几(大于0)
     */
    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)
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }
    // ===============================List 列表=================================
    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 获取list缓存的长度
     *
     * @param key 键
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0
     *              时,-1,表尾,-2倒数第二个元素,依次类推
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return 赋值结果
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return 赋值结果
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return 赋值结果
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
        try {
            return redisTemplate.opsForList().remove(key, count, value);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    // ============================Set 集合=============================
    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0) {
                expire(key, time);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 获取set缓存的长度
     *
     * @param key 键
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().remove(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    // ================================Hash 哈希=================================
    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }
    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }
    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * HashSet 并设置时间
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 向一张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   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            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);
    }
    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }
    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }
    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }
}

        4)使用redis

//web层 打通 service层
    @Autowired
    private IDemoUsersService iDemoUsersService;
    @Autowired
    private RedisUtil redisUtil;//注入redis工具类


    @GetMapping("/getUserInfoById")
    public Result getUserInfoById(Long userId){
        String key = "user"+userId;
        //缓存是否有这个对象
        boolean b = redisUtil.hasKey(key);
        if(b){//如果有
            log.info("我是缓存中获取的");
            DemoUsers demoUsers = (DemoUsers) redisUtil.get(key);
            return Result.ok(demoUsers);
        }else{//如果缓存没有
            log.info("我是数据库获取的");
            DemoUsers demoUsers = iDemoUsersService.getById(userId);
            //往缓存中丢一份
            redisUtil.set(key,demoUsers);
            return Result.ok(demoUsers);
        }
    }

5)相关注解:

@cacheable(cacheNames="userInfo",key="#userId",unless="#userId == 1",condition="#userId == 1")
//缓存组件名、 key名、 userId=1 时不使用缓存、userId=2 时使用缓存

        

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值