作者简介:大家好,我是撸代码的羊驼,前阿里巴巴架构师,现某互联网公司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指值