SpringBoot

作者简介:大家好,我是撸代码的羊驼,前阿里巴巴架构师,现某互联网公司CTO

联系v:sulny_ann,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

springboot介绍

Spring Boot是基于Spring开发的全新框架,相当于对Spring做了又一层封装。但是对于SpringMVC、Mybatis还是引入它的依赖的,需要配置的,但是配置是很简单的,甚至很多时候是不需要进行配置的。

该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。

boot工程的创建

学习一个新技术:看一下它的快速入门即可,不需要将技术文档都略读

打开idea,点击新建项目,弹出下面的图,写好名称与高级设置里面的组,点击确定就可以了

运行的环境是jdk8

检查

maven 3.5以上版本

打开maven的setting文件

配置本地仓库:

配置阿里云镜像

配置maven编译版本

依赖方面

1、添加一下父工程

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
    </parent>

2、添加依赖【依赖不用添加版本号】

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

创建启动类

创建一个类在其实加上@SpringBootApplication注解标识为启动类

@SpringBootApplication
public class HelloApplication {

    public static void main(String[] args) {
//将本类的字节码对象传进去,还有main方法中的args
        SpringApplication.run(HelloApplication.class, args);
    }
}

定义Controller

创建Controller,Controller要放在启动类所在包或者其子包下

@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String hello(){
        return "hello";
    }
}

运行测试

在spring项目中需要配置tomcat,但是在boot项目中直接运行启动类就可以了

当不在idea中运行,该如何运行呢?

我们可以把springboot的项目打成jar包直接去运行。而不是通过运行main方法

SpringBoot项目是依赖于Maven构建的,但打包时如果只依赖 Maven打包工具则会打包不完整,我们还需要在SpringBoot项目中引入打包插件 。如果不添加SpringBoot打包插件,打包是不完整的,也无法运行jar包

添加maven打包插件

    <build>
        <plugins>
            <!--springboot打包插件-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

点击maven的生命周期package

复制这个只到target的目录:

在此电脑中进行查询,查询在这个目录后,在这个目录下进行cmd操作:

输入java -jar 跟jar包名字【按tab键自动补全】,如java -jar SpringbootTest-1.0-SNAPSHOT.jar

依赖冲突及其解决方案

Ajar包中依赖Cjar包它是低版本,Bjar包也依赖Cjar包,但是它依赖的Cjar包是更高的版本。当在xml中是根据依赖引入的顺序来指定使用哪一个的,当你第一个引入的是Ajar包,在引入Bjar包,则B调用C中的其实是使用的低版本的C,但是低版本的C的方法没有B的需求,这时候,就出现了依赖冲突的了

如果出现了类似于 java.lang.ClassNotFoundException,Method not found: 这些异常检查相关的依赖冲突问题,排除掉低版本的依赖,留下高版本的依赖即可解决。

我们有一个插件Maven Helper,可以帮助我们解决

当下载好这个插件的时候,打开pom文件,在最下面有一个Dependency Analyzer,点击它,如果有依赖冲突,在这个就会有显示

boot中的版本锁定就可以很好的避免掉依赖冲突

即我们仅仅引入父工程,当引入一些其他的依赖,我们是不用指定版本号的,因为父工程中已经将它们锁定指定了

springboot的starter启动器机制

针对某一个场景会设置一个启动器,当我们使用这个场景的时候,只要使用这个starter的启动包就可以了

例如:spring-boot-starter-web,使用开发web场景的时候使用这个启动器,它下面直接有json、tomcat、webmvc

还有一些非springboot下的启动器,它们不是以spring-boot开头的,例如mybatis-spring-boot-starter

自动配置源码解析【后面学习】

当我们需要使用什么场景时,就会自动配置这个场景相关的配置。

自定义配置

这中配置是程序员对默认配置进行的修改的操作

配置文件有properties格式与yml格式,他们是一种专门用来写配置文件的语言,比xml文件更加的简便

比如,在xml中

<student>
    <name>sangeng</name>
    <age>15</age>
</student>

在yml中

student:
    name: sangeng
    age: 15

当启动类启动的时候会默认的读取application.yml,application是默认的名字,如果用别的名字是读取不了的,要求放在resources

创建yml文件,直接选择file,输入application.yml回车

语法

1、java中对于驼峰命名法,可用原名或使用-代替驼峰,如java中的lastName属性,在yml中使用lastName或 last-name都可正确映射

2、注释用#

3、k: v 表示键值对关系

4、使用空格的缩进表示层级关系,只要是左对齐的一列数据,都是同一个层级的

5、yml文件中字符串可以不加单引号或者双绰号

6、日期使用反斜杠划分2022/10/14

7、对象的写法

student:

name: zhangsan

age: 20

行内写法:student: {name: zhangsan,age: 20}

8、 集合、数组的写法

pets:

- dog

- pig

- cat

行内写法:pets: [dog,pig,cat]

9、对象数组

students:

- name: zhangsan

age: 22

- name: lisi

age: 20

使用占位符进行赋值

有时候多个配置它们的值可能是有关联的,当我们需要另一个配置的值时候,就可以使用占位符就行关联

可以使用 ${key:defaultValue} 的方式来赋值,若key不存在,则会使用defaultValue来赋值

如:

server:

port: ${myPort:88}

myPort: 80

SpringBoot读取YML文件中的配置

读取yml文件中的单个值

@RestController
public class TestController {
    @Value("${student.lastName}")
    private String lastName;
    @RequestMapping("/test")
    public String test(){
        System.out.println(lastName);
        return "hi";
    }
}

@ConfigurationProperties将配置文件中整个东西都进行读取出来,并可以赋值给相应的对象

student:
  name: sangeng
  age: 17

将实体类上添加注解@Component 和@ConfigurationProperties(prefix = "配置前缀")

这样读取到的内容就会映射到实体类上

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
@ConfigurationProperties(prefix = "student")  //读取配置文件下的student的东西
public class Student {
    private String name;
    private Integer age;
}

热部署

当我们运行了程序后,如果对程序进行了修改,程序会自动重启

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

boot整合Junit进行单元测试

Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库,之前boot版本是JUnit 4作为单元测试默认库

1、添加依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

2、在src下创建一个test文件夹,在它的下面创建与启动类所在的目录一样的文件夹

选择项目结构,选中test中的下面的Java,把它变为test

创建测试类;给该类添加 @SpringBootTest注解;在类中定义测试方法,在该方法上加 @Test注解。注意测试类中引入的包只要从com那就可以了,如com.longhua

在测试的过程中,一般需要在spring容器中获取容器中对象,来进行测试,测试里面的方法

从容器中获取对象:直接 @Autowired相应的service即可

3、关于junit4老版本的写法

同样在测试方法中增加 @Test注解,不同的junit4导入的是import org.junit.Test,junit5导入的是import org.junit.jupiter.api.Test;;在类上添加 @SpringBootTest 和@RunWith(SpringRunner.class)才可以

boot整合mybatis

前期准备工作

1、准备表User

2、构建User实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String username;
    private Integer age;
    private String address;
}

整合步骤

1、依赖

        <!--mybatis启动器-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

2、boot整合mybatis配置数据库信息 application.yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

3、配置mybatis的映射xml文件的位置,到时候,他就会到这里找它的映射文件,所有的mabatis的配置都是在mybatis关键字下面进行配置

mybatis:
 #它就相当于在spring整合mybatis在resources中创建的目录,这里指定映射文件在那里
 #classpath表示是类加载路径下,因为resources目录下的东西就是表示在类加载路径下的
  mapper-locations: classpath:mapper/*Mapper.xml  
  type-aliases-package: com.hyz.domain   #配置哪个包下的类有默认的别名

4、创建mapper包,这个包就是在spring整合mybatis我们写的dao包,只是名字发生了改变

这里面同样是接口,接口名统一是类+Mapper,不同的是需要在该接口上添加@Mapper注解,这个注解是给mybatis看的,让它直到mapper接口在这里

@Mapper
public interface UserMapper {
    List<User>  findAll();
}

同样的写xml的时候,将鼠标放在接口名,ALT+回车,在第五行,选择我们在resources中创建的mapper文件夹,会自动形成它的xml,再鼠标放在方法上,alt+回车,选择第三行。

使用boot进行web开发

为了演示web的内容,我们新建了一个模块

文件--->项目结构【这样创建的项目就会和之前的项目在一起展示了,不会出现一个窗口就显示一个项目】

将pom文件中的jdk8的版本说明删去,这个是没有用的

定义启动类,添加main方法:

@SpringBootApplication
public class StaticApplication {
    public static void main(String[] args) {
        SpringApplication.run(StaticApplication.class,args);
    }
}

把静态资源放到boot中,并用浏览器就行访问

静态资源放在哪里?

答:在resources中创建static文件,将静态资源放在这里

放在这里怎么进行静态资源访问?

答:如果想访问文件:resources/static/index.html 只需要在访问时资源路径写成/index.html即可。 如果想访问文件:resources/static/pages/login.html 访问的资源路径写成: /pages/login.html

设置请求映射规则

因为springboot是整合了springmvc,所以在web开发的内容都是springmvc的内容

  • @PostMapping 等价于 @RequestMapping(method = RequestMethod.POST)
  • @GetMapping 等价于 @RequestMapping(method = RequestMethod.GET) ​
  • @PutMapping 等价于 @RequestMapping(method = RequestMethod.PUT) ​
  • @DeleteMapping 等价于 @RequestMapping(method = RequestMethod.DELETE)

@RequestMapping注解可以使用类上和方法中,在类上,表示在请求方法时都要以它的路径为开头进行请求方法

指定请求参数

参数必须要有某个:params = "code"

不能有code这个参数:params = "!code"

参数必须有某个值,使用 params = "参数名=参数值"

参数值必须不是某个值:params = "参数名!=参数值"

注意:不仅仅RequestMapping有着些属性,GetMapping注解也有的

指定请求头

请求头中具有deviceType的请求:headers = "deviceType"

请求头不能有deviceType:headers = "!deviceType"

有deviceType这个请求头,并且其值必须是某个值:headers = "deviceType=ios"

有deviceType这个请求头,并且其值必须不是某个值:headers = "deviceType!=ios"

有Content-Type头值为 multipart/from-data:consumes = "multipart/from-data"(文件上传时要指定这个才能被我们的方法进行处理)

获取请求参数

1、获取路径参数

@RestController
public class HelloController {
    @GetMapping("/user/{id}")
    public String hello(@PathVariable("id") Integer id){
        return "hello";
    }}

测试:localhost:8080/user/1

2、获取请求体中的Json参数

配置:spring-boot-starter-web,它里面有json依赖,所以不需要导入依赖了,也不需要添加注解驱动,就可以直接识别注解 @RequestMapping 、 @PostMapping

@RequestBody===>注解获取请求体中的数据

@ResponseBody===>将返回值放到响应体中

1、获取的数据映射到单类上

@RestController 
//RestController = Controller + ResponseBody
public class UserController {
    @PostMapping("/user")
    public String insertUser(@RequestBody User user){
        System.out.println(user);
        return "insertUser";
    }
}
@RequestBody会将请求体中的数据映射到实体类中,注意postman中的 键必须与我的实体类的属性名一样,不然不会进行正确的映射,得到的结果也就是null了。

2、获取参数封装成Map集合

用Map接收,前端也是放在json中的,仅仅是我们拿取值使用key的方式来拿的

     @PostMapping("/user")
    public String insertUser(@RequestBody Map map){
        System.out.println("insertUser");
        System.out.println(map); 
        return "insertUser";
    }

测试:{"name":"san", "age":13}

结果:{name=san, age=13}

关于map的key-value,它会将postman中写的json的键与值放在这里面进行存储,其中使用等号进行连接

   @GetMapping("/test")
    public void downloadOneUserIllness(@RequestBody Map map){
        System.out.println(map.get("name"));
        System.out.println(map.get("illness"));
    }

json数据

{
"name":"张三",
"illness":"血脂"
}

打印结果

张三

血脂

3、如果请求体传递过来的数据是User集合

     @PostMapping("/user")
    public String insertUsers(@RequestBody List<User> users){
        System.out.println("insertUsers");
        System.out.println(users);
        return "insertUser";
    }

测试:[{"name":"三更1","age":14},{"name":"三更2","age":15},{"name":"三更3","age":16}]

注意:如果需要使用@RequestBody来获取请求体中Json并且进行转换,要求请求头 Content-Type的值要为:application/json

3、获取QueryString格式参数

QueryString格式的参数是直接写在连接的后面的,请求路径问号后面就是参数,多个参数之间使用&进行连接

注意,如果方法中的参数名与请求参数的名是一样的,则默认就能够获取请求参数值,如不一致,还需要加上@RequestParam

@GetMapping("/user")
    public String addUser(Integer id,@RequestParam("Name") String name){
        System.out.println(id+name);
        return "success";
}}

@RequestParam("Name")String name,表示将路径中键为Name的值获取放到我的参数name上

当请求路径中例如是爱好参数,它属于数据格式的,在请求路径是这样写的:http://localhost:8080/user/likes=唱歌&likes=跳舞&likes=跑步。在controller中的方法使用String[] likes数组进行接收

同理请求中的参数,也可以将它映射为User对象

 @GetMapping("/user")
    public String addUser(User user){
        System.out.println(user);
        return "success";
    }

public class User {
    private Integer id;
    private String name;
    private Integer age;
    private String [] likes;
}

测试:http://localhost:8080/user?name=zhangsan&age=19&likes=跳舞&likes=唱歌

此时,实体类中的成员变量要和请求参数名对应上

请求注解的其他属性

QueryString 格式 RequestParm、请求体RequestBody、路径注解PathVariable,这三个注解的属性

其中公共属性有:required,表示该参数是否必须要传递,默认是必须,可以指定为false,表示既可以传也可以不传。

例如:我将uid的required 属性设置为false,则我在postman中可以不传递参数id,则sout出来的值就为null,当你觉得这个参数不一定要传递,就可以这样使用

 @GetMapping("/user")
    public String addUser(@RequestParam(value = "id",required = false) Integer uid,
                          @RequestParam("name") String name, @RequestParam("likes")String[] likes){
        System.out.println(uid);
        return "success";
    }

其公共属性还有:defaultValue,一般在RequestParm可能会用到,表示该参数如果没有传递的话,设置该参数的默认值

 @GetMapping("/user")
    public String addUser(@RequestParam(value = "id",required = false,defaultValue = "777") Integer uid,
                          @RequestParam("name") String name, @RequestParam("likes")String[] likes){
        System.out.println(uid);
        return "success";
 }

在boot下将数据转换为json放到响应体

前后端分离的项目最终的返回值都会放到响应体重

boot其实是借助springmvc的框架帮助我们来实现这个功能的

@ResponseBody可以把Json放到响应体中。@ResponseBody可以加在类上和方法上。我们之前是加在controller类上的与controller注解结合变成RestController

以前使用springmvc,当然需要引入jackson依赖,并且开启springmvc控制controller中的注解驱动,but now使用boot,这些都不需要了,web启动器包含了jackson,驱动更不用加了,默认有了

 @RequestMapping("/user/{id}")
 @ResponseBody
    //ResponseBody 如果整个controller都是需要将返回结果放在响应体中,那这个注解可以放在类上
    public User findById(@PathVariable("id") Integer id){
        User user = new User(id, "sange", 15, null);
        return user;
    }

测试:http://localhost:8080/user/1

响应体结果:{"id":1,"name":"sange","age":15,"likes":null}

@ResponseBody就是将方法的返回值以json格式放到响应体中

前后端分离准备工作

将前端的代码放在

点击文件,项目结构

此工程还不是boot工程,我们需要引入父工程依赖,并加上web启动器和lombok

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
</dependencies>

创建启动类:

@SpringBootApplication
public class WebApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebApplication.class,args);
    }
}

至此boot工程搭建完成

当我们在本地启动静态工程与web工程,默认的两个工程的端口都是8080,则会冲突,可以设置application.yml文件来指定它们的端口

后端基本骨架

查询数据库的接口写好了,就等着整合mybatis了

整合mybatis

添加mybatis启动器与mysql依赖

       <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

连接数据库的操作:我在idea中设置了它的模板直接创建就好了

到时候用mybatis插件将XXXmapper.xml存放在这里

测试:http://localhost:8080/user/getAll

结果:得到响应体的数据

统一返回结果

即上面在响应体中我们显示的数据仅仅只有date数据,现在我们还需要一个code、msg信息,统一的json响应结果给前端,我们把响应结果放在包common中

package com.longhua.common;

import com.fasterxml.jackson.annotation.JsonInclude;
/**
统一响应结果处理
 */
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> {
    /**
     * 状态码
     * 1成功,0和其它数字为失败
     */
    private Integer code;
    /**
     * 提示信息,如果有错误时,前端可以获取该字段进行提示
     */
    private String msg;
    /**
     * 查询到的结果数据,
     */
    private T data;



    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public ResponseResult(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public ResponseResult(Integer code, T data) {
        this.code = code;
        this.data = data;
    }
    public ResponseResult(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
}

修该controller方法的返回值

前端版-前端请求发送请求代码编写

 <!-- 开发环境版本,包含了有帮助的命令行警告 -->
   <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
   <script src="https://unpkg.com/axios/dist/axios.min.js"></script>

   <script>
      var v = new Vue({
          el:"#app",
          created(){
              this.findAll();
          },
          methods:{
              findAll(){
                  //请求后台接口  把接受到的数据渲染展示在页面中
                  axios.get("http://localhost/user/findAll").then((res)=>{
                      console.log(res);
                  })
              }
          }
      });
   </script>

此时请求出发生跨域的问题

解决跨域问题

什么是跨域请求?

浏览器出于安全的考虑,必须要求源相同才能正常进行通信,即协议。域名、端口号都完全一致。当请求的域名与端口不一样浏览器是不被允许的。我们解决了CORS问题就可以解决跨域的问题。

boot的解决办法:

在controller类上加上注解 @CrossOrigin即可,但是以后的项目可能写的controller类比较多,这种方式就显的比较繁琐,所以使用下面的方式,达到一劳永逸的效果

新建一个config包,以后的配置类都放在这个,关于配置类我们都要写注解 @Configuration表示当前类是配置类,并把它放在spring容器中。

前端版-前端数据渲染分析

前端的data属性就是我们放到响应体的内容

登录案例

在前后端分离的场景中,很多时候会采用token的方案进行登录校验

​ 登录成功时,后端会根据一些用户信息(如id)生成一个token字符串返回给前端

​ 前端会存储这个token。以后前端发起请求时如果有token就会把token放在请求头中发送给后端

​ 后端接口就可以获取请求头中的token信息进行解析,如果解析不成功说明token超时了或者不是正确的token,相当于是未登录状态,如果解析成功,说明前端是已经登录过的

注意移动端是没有session的。

因为有些状态时需要登录成功后才可以进行访问的,为了证明其他请求是登录状态的,这个请求头中需要存放token值,后端获取它进行解析,解析成功说明它确实是登录状态的。

这个方案可以用于移动端与网页端

Token生成方案-JWT

1、jwt依赖

<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
</dependency>

2、创建untils包,用于存放以后的工具类

直接复制下面的代码,然后在idea的这个包下ctrl+v就可以自动创建了这个类,很方便

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

/**
 * JWT工具类
 */
public class JwtUtil {

    //有效期为
    public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000  一个小时
    //设置秘钥明文
    public static final String JWT_KEY = "sangeng";

    /**
     * 创建token
     * @param id
     * @param subject
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String id, String subject, Long ttlMillis) {

        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if(ttlMillis==null){
            ttlMillis=JwtUtil.JWT_TTL;
        }
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        SecretKey secretKey = generalKey();

        JwtBuilder builder = Jwts.builder()
                .setId(id)              //唯一的ID
                .setSubject(subject)   // 主题  可以是JSON数据
                .setIssuer("sg")     // 签发者
                .setIssuedAt(now)      // 签发时间
                .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
                .setExpiration(expDate);// 设置过期时间
        return builder.compact();
    }

    /**
     * 生成加密后的秘钥 secretKey
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }
    
    /**
     * 解析
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }


}

测试这个token生成工具类

 public static void main(String[] args) throws Exception {
        //创建token
        String token = JwtUtil.createJWT(UUID.randomUUID().toString(), "sange", null);
        System.out.println(token);
        //解析token,拿到之前的subject
        Claims claims = JwtUtil.parseJWT(token);
        String subject = claims.getSubject();
        System.out.println(subject);

    }

结果:eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI1NDE2NjNjNi0wODgyLTQ3NmMtOTFmMy0yNzE3NTMxMTI2ZjAiLCJzdWIiOiJzYW5nZSIsImlzcyI6InNnIiwiaWF0IjoxNjY5Nzc1MTU5LCJleHAiOjE2Njk3Nzg3NTl9.HiCVGjqftIe_c3eOB-Vqf0kn0OHGLzc3OrA-C-kbkrY

sange

当我们可以解析出来,表示它是登录状态的,当解析出现问题,就表示它是未登录状态

token登录接口的实现

package com.sange.controller;

import com.sange.common.ResponseResult;
import com.sange.domain.SystemUser;
import com.sange.service.SystemUserService;
import com.sange.utils.JwtUtil;
import com.sange.common.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;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@RestController
@RequestMapping("/sys_user")
public class SystemUserController {
    @Autowired
    private SystemUserService userService;

    @PostMapping("/login")
    public ResponseResult login(@RequestBody SystemUser user) {
        //校验用户名密码是否正确
        SystemUser loginUser = userService.login(user);
        Map<String, Object> map;
        if (loginUser != null) {
            //如果正确 生成token返回,token里面存放的值是用户id
            map = new HashMap<>();
            String token = JwtUtil.createJWT(UUID.randomUUID().toString(), String.valueOf(loginUser.getId()), null);
            //因为我们返回的是“token:值”的形式给前端的,所以加入了map
            map.put("token", token);
        } else {
            //如果不正确 给出相应的提示
            return new ResponseResult(300, "用户名或密码错误,请重新登录");
        }
        return new ResponseResult(200, "登录成功", map);
    }
}

我们将token放在map里面传给前端,经过json解析,到前端的形似是个json对象的形式,data属性里面是键值对放在大括号里面

以上的操作是给前端一个token

拦截器

前端以后发送请求的时候,我们就需要判断这个请求头中是否含有token,但是后期的请求是有很多的,我们在每一个请求中都进行获取请求头中的token进行判断很麻烦的,我们就可以使用拦截器来操作

拦截器的使用使用步骤

1、创建拦截器interceptor包

2、创建类实现HandlerInterceptor接口,实现方法【类加上 @Component注解注入spring容器】

3、配置拦截器WebMvcConfigurer类提供了mvc的一些配置,使用类的方式来代替mvc的配置【为了设置拦截的路径】

注意boot中将以前写的各种配置文件的形似,都会以配置类的形式来完成相同的工作

拦截器

package com.sange.interceptor;

import com.sange.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取请求头中的token
        String token = request.getHeader("token");
        //判断token是否为空,如果为空也代表未登录 提醒重新登录
        //hasText函数见名知意,表示有的情况,即为真,加上!就表示否的情况
        if(!StringUtils.hasText(token)){
            response.sendError(0);
            return false;
        }
        //解析token看看是否成功
        try {
            Claims claims = JwtUtil.parseJWT(token);
            String subject = claims.getSubject();
            System.out.println(subject);
        } catch (Exception e) {
            e.printStackTrace();
            //如果解析过程中没有出现异常说明是登录状态
            //如果出现了异常,说明未登录,提醒重新登录
            response.sendError(0);
            return false;
        }
        return true;
    }
}

配置要拦截的路径与要放行的路径

package com.sange.config;

import com.sange.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class LoginConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)//添加拦截器
            //配置拦截路径
            .addPathPatterns("/**")  
            //因为我的静态资源不在同一个工程中,所以对静态资源不用拦截
            //配置排除路径,排除登录的请求
            .excludePathPatterns("/sys_user/login");
    }
}
注意实现 WebMvcConfigure 接口的话,不会拦截 Spring Boot 默认的静态资源。这样就非常的友好了,我们不用写排除静态资源的内容了

boot下的统一异常处理

boot也是使用springmvc的方案进行处理的

我们在controller中写了很多的方法,目前我们是没有对异常进行处理的,在处理异常的情况下,即使出现了异常,我们都要使用统一的json字符串的形式进行返回。

放在common包中

写一个异常统一处理类,类上添加 @ControllerAdvice注解,表示当controller出现异常,这个异常与我定义的异常一样,这回执行这个异常处理方法。这里为了测试,将异常取了最大的范围RuntimeException

@ControllerAdvice
public class MyControllerAdvice {

    @ExceptionHandler(RuntimeException.class)
    @ResponseBody
    //前后端分离的项目会将函数的返回值设置为写的统一响应格式的类
    public ResponseResult handlerException(Exception e){
        //获取异常信息,存放如ResponseResult的msg属性
        String message = e.getMessage();
        ResponseResult result = new ResponseResult(300,message);
        //把ResponseResult作为返回值返回,当发生异常的时候会将数据直接放在响应体中
        return result;
    }
}

这样我们在对token没有获取的时候进行异常处理就可以使用这个异常统一处理了

package com.sange.interceptor;

import com.sange.exception.MyControllerAdvice;
import com.sange.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取请求头中的token
        String token = request.getHeader("token");
        //判断token是否为空,如果为空也代表未登录 提醒重新登录
        //hasText函数见名知意,表示有的情况,即为真,加上!就表示否的情况
        if(!StringUtils.hasText(token)){
            //抛出异常别异常统一处理拿到,进行处理,返回统一的格式
            throw new RuntimeException("未登录,请重新登录");
        }
        //解析token看看是否成功
        try {
            Claims claims = JwtUtil.parseJWT(token);
            String subject = claims.getSubject();
            System.out.println(subject);
        } catch (Exception e) {
            e.printStackTrace();
            //如果解析过程中没有出现异常说明是登录状态
            //如果出现了异常,说明未登录,提醒重新登录
            throw new RuntimeException("未登录,请重新登录");
        }
        return true;
    }
}

获取web原生对象

当需要使用request对象,response,session对象等,我们只需要在controller方法上添加对应类型的参数即可

 @RestController
public class TestController {

    @RequestMapping("/getRequestAndResponse")
    public ResponseResult getRequestAndResponse(HttpServletRequest request, HttpServletResponse response, HttpSession session){
        System.out.println(request);
        return new ResponseResult(200,"成功");
    }

自定义参数解析

如果我们想实现像获取请求体中的数据那样,在controller方法的参数上增加一个@RepuestBody注解就可以获取到对应的数据的话。例如我想写一个注解就可以拿到请求体中的token,然后解析拿到id,我们想加上一个注解就可以拿到token

传统上是这样写的

传统上这样写我们如果想在一个方法中获取这个id,都必须写上上面的一段代码,为了提高代码的复用性与方便性

现在boot可以使用HandlerMethodArgumentResolver来实现自定义的参数解析。

1、创建resolver解析器包

2、在这个包中定义一个注解,我这里的注解是为了获取用户的id

 @Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUserId {

}

3、在这个包中创建类实现HandlerMethodArgumentResolver接口并重写其中的方法

 @Component
public class UserIdArgumentResolver implements HandlerMethodArgumentResolver {

    //判断方法参数使用能使用当前的参数解析器进行解析
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        //如果方法参数有加上CurrentUserId注解,就能把被我们的解析器解析
        return parameter.hasParameterAnnotation(CurrentUserId.class);
    }
    //进行参数解析的方法,可以在方法中获取对应的数据,然后把数据作为返回值返回。方法的返回值就会赋值给对应的方法参数
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        //获取请求头中的token
        String token = webRequest.getHeader("token");
        if(StringUtils.hasText(token)){
            //解析token,获取userId
            Claims claims = JwtUtil.parseJWT(token);
            String userId = claims.getSubject();
            //返回结果
            return userId;
        }
        return null;
    }
}

4、在config包中配置参数解析器

 @Configuration
public class ArgumentResolverConfig implements WebMvcConfigurer {

    @Autowired 
    private UserIdArgumentResolver userIdArgumentResolver;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(userIdArgumentResolver);
    }
}

最后我们使用注解就可以拿到id

boot下声明式事务

以前在springmvc的情况下要是使用事务的时候需要进行一些配置,现在我们只需要在需要添加事务操作,增加一个注解@Transactional

当没有添加事务,这个插入的操作只有添加user,当添加了事务,这个两个操作都不会插入进去,user数据进性了回滚,因为事务具有同一性。

boot整合Redis

1、引入redis依赖

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

2、yml中配置redis的地址与端口号

spring:
  redis:
    host: 127.0.0.1 #redis服务器ip地址
    port: 6379  #redis默认端口号

在windows中启动redis

使用redis

  @Autowired
    private StringRedisTemplate redisTemplate;
    @Test
    public void testRedis(){
        redisTemplate.opsForValue().set("name","张三");
    }
    @Test
    public void getRedis(){
        String name = redisTemplate.opsForValue().get("name");
        System.out.println(name);
    }

boot整合test

1、依赖

 <!--boot下的test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

2、在test目录下创建与启动类包结构一样的包,创建类WebTest,在类上加上SpringBootTest注解

在类里面就可以写测试方法了,在方法中加入注解Test即可

boot的环境切换

我们必须用application-xxx.yml的命名方式创建配置文件,其中xxx可以根据自己的需求来定义。

例如我们需要一个测试环境的配置文件,则可以命名为:application-test.yml;需要一个生产环境的配置文件,可以命名为:application-prod.yml

我们可以再application.yml文件中使用spring.profiles.active属性来配置激活哪个环境。 如spring.profiles.active

spring:
	profiles:
		active: prod   #这样就激活了生产环境,这里写的就是xxx指值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值