Springboot笔记-2
Springboot2.5学习笔记-2
三更草堂
参考三更老师笔记,仅供学习使用,侵删。
源码(我自己的):https://xingqiu-tuchuang-1256524210.cos.ap-shanghai.myqcloud.com/13671/springboot_study.zip
15.Web开发
1.静态资源访问
1. 由于SpringBoot的项目是打成jar包的所以没有之前web项目的那些web资源目录(webapps)。那么我们的静态资源要放到哪里呢?—->SpringBoot官方文档中说把静态资源放到 resources/static
(或者 resources/public
或者resources/resources
或者 resources/META-INF/resources
) 中即可。
- 静态资源放完后,
- 例如我们想访问文件:resources/static/index.html 只需要在访问时资源路径写成/index.html即可。
- 例如我们想访问文件:resources/static/pages/login.html 访问的资源路径写成: /pages/login.html
2.修改静态资源访问路径
- SpringBoot默认的静态资源路径匹配为/** 。如果想要修改可以通过
spring.mvc.static-path-pattern
这个配置进行修改。 - 例如想让访问静态资源的url必须前缀有/res。例如/res/index.html 才能访问到static目录中的。我们可以修改如下:
- 在application.yml中
1 | spring: |
- 注:查看源码配置时要注意数据类型才知道如何配置。
3.修改静态资源存放目录
- 我们可以修改 spring.web.resources.static-locations 这个配置来修改静态资源的存放目录。
- 例如:
1 | spring: |
4.设置请求映射规则@RequestMapping
注:springmvc知识
详细讲解:https://www.bilibili.com/video/BV1AK4y1o74Y P5-P12
该注解可以加到方法上或者是类上(查看其源码可知)。我们可以用其来设定所能匹配请求的要求。只有符合了设置的要求,请求才能被加了该注解的方法或类处理。
4.1(4.1-4.4指定请求)指定请求路径:
path或者value属性都可以用来指定请求路径。
例如:
- 我们期望让请求的资源路径为**/test/testPath的请求能够被testPath**方法处理则可以写如下代码
1 |
|
- 类上也可以加,这个和下面这个功能一样:
1 |
|
4.2指定请求方式:
@RequestMapping注解中的method属性可以指定请求方式:我们期望让请求的资源路径为**/test/testMethod的POST请求能够被testMethod**方法处理。则可以写如下代码
1
2
3
4
5
6
7
8
9
10
public class TestController {
public String testMethod(){
System.out.println("testMethod处理了请求");
return "testMethod";
}
}- 可以ctrl+左键点进method发现对象数组,再点发现枚举类以及可以编写的请求方式,一般这种都是枚举类。method = Request.直接写名称就行。
也可以用注解来进行替换:
- @PostMapping 等价于 @RequestMapping(method = RequestMethod.POST)
- @GetMapping 等价于 @RequestMapping(method = RequestMethod.GET)
- @PutMapping 等价于 @RequestMapping(method = RequestMethod.PUT)
- @DeleteMapping 等价于 @RequestMapping(method = RequestMethod.DELETE)
例如:
上面的需求我们可以使用下面的写法实现
1
2
3
4
5
6
7
8
9
10
public class TestController {
public String testMethod(){
System.out.println("testMethod处理了请求");
return "testMethod";
}
}指定请求参数
我们可以使用params属性来对请求参数进行一些限制。可以要求必须具有某些参数,或者是某些参数必须是某个值,或者是某些参数必须不是某个值。
例如:
- 我们期望让请求的资源路径为**/test/testParams的GET请求,并且请求参数中具有code参数**的请求能够被testParams方法处理。则可以写如下代码
1
2
3
4
5
6
7
8
9
public class TestController {
public String testParams(){
System.out.println("testParams处理了请求");
return "testParams";
}
}- 如果是要求不能有code这个参数可以把改成如下形式
1
2
3
4
5
6
7
8
9
public class TestController {
public String testParams(){
System.out.println("testParams处理了请求");
return "testParams";
}
}- 如果要求有code这参数,并且这参数值必须是某个值可以改成如下形式
1
2
3
4
5
6
7
8
9
public class TestController {
public String testParams(){
System.out.println("testParams处理了请求");
return "testParams";
}
}- 如果要求有code这参数,并且这参数值必须不是某个值可以改成如下形式
1
2
3
4
5
6
7
8
9
public class TestController {
public String testParams(){
System.out.println("testParams处理了请求");
return "testParams";
}
}
4.3指定请求头:
- 使用headers属性来对请求头进行一些限制。
例如:
- 我们期望让请求的资源路径为**/test/testHeaders的GET请求,并且请求头中具有deviceType**的请求能够被testHeaders方法处理。则可以写如下代码
1
2
3
4
5
6
7
8
9
10
public class TestController {
public String testHeaders(){
System.out.println("testHeaders处理了请求");
return "testHeaders";
}
}- 如果是要求不能有deviceType这个请求头可以把改成如下形式
1
2
3
4
5
6
7
8
9
10
public class TestController {
public String testHeaders(){
System.out.println("testHeaders处理了请求");
return "testHeaders";
}
}- 如果要求有deviceType这个请求头,并且其值必须是某个值可以改成如下形式
1
2
3
4
5
6
7
8
9
10
public class TestController {
public String testHeaders(){
System.out.println("testHeaders处理了请求");
return "testHeaders";
}
}- 如果要求有deviceType这个请求头,并且其值必须不是某个值可以改成如下形式
1
2
3
4
5
6
7
8
9
10
public class TestController {
public String testHeaders(){
System.out.println("testHeaders处理了请求");
return "testHeaders";
}
}
4.4指定请求头Content-Type:
使用consumes属性来对Content-Type这个请求头进行一些限制。
1.1 范例一
- 我们期望让请求的资源路径为**/test/testConsumes**的POST请求,并且请求头中的Content-Type头必须为 multipart/from-data 的请求能够被testConsumes方法处理。则可以写如下代码
1
2
3
4
5
public String testConsumes(){
System.out.println("testConsumes处理了请求");
return "testConsumes";
}1.2 范例二
- 如果我们要求请求头Content-Type的值必须不能为某个multipart/from-data则可以改成如下形式:
1
2
3
4
5
public String testConsumes(){
System.out.println("testConsumes处理了请求");
return "testConsumes";
}
4.5(4.5-4.8获取参数)获取请求参数:
RestFul风格:原来的请求参数加入到请求资源地址中。然后原来请求的增,删,改,查操作。改为使用HTTP协议中请求方式GET、POST、PUT、DELETE表示。
1
2
3
4
5
6
7比如:http://ip:port/工程名/资源名?请求参数 举例:http://127.0.0.1:8080/springmvc/book?action=delete&id=1
restful风格是:
比如:http://ip:port/工程名/资源名/请求参数/请求参数 举例:http://127.0.0.1:8080/springmvc/book/1
请求的动作删除由请求方式delete决定restful风格中请求方式GET、POST、PUT、DELETE分别表示查、增、改、删。
1
2
3
4
5
6
7
8
9
10
11
12GET请求 对应 查询
http://ip:port/工程名/book/1 HTTP请求GET 表示要查询id为1的图书
http://ip:port/工程名/book HTTP请求GET 表示查询全部的图书
POST请求 对应 添加
http://ip:port/工程名/book HTTP请求POST 表示要添加一个图书
PUT请求 对应 修改
http://ip:port/工程名/book/1 HTTP请求PUT 表示要修改id为1的图书信息
DELETE请求 对应 删除
http://ip:port/工程名/book/1 HTTP请求DELETE 表示要删除id为1的图书信息
获取路径参数:RestFul风格()的接口一些参数是在请求路径上的。类似: /user/1 这里的1就是id。
如果我们想获取这种格式的数据可以使用**@PathVariable**来实现。
1.1 范例一
- 要求定义个RestFul风格的接口,该接口可以用来根据id查询用户。请求路径要求为 /user ,请求方式要求为GET。
- 而请求参数id要写在请求路径上,例如 /user/1 这里的1就是id。
- 我们可以定义如下方法,通过如下方式来获取路径参数:
1
2
3
4
5
6
7
8
9
10
public class UserController {
public String findUserById( { Integer id)
System.out.println("findUserById");
System.out.println(id);
return "findUserById";
}
}1.2 范例二
- 如果这个接口,想根据id和username查询用户。请求路径要求为 /user ,请求方式要求为GET。
- 而请求参数id和name要写在请求路径上,例如 /user/1/zs 这里的1就是id,zs是name
- 我们可以定义如下方法,通过如下方式来获取路径参数:
1
2
3
4
5
6
7
8
9
10
public class UserController {
public String findUser({ Integer id, String name)
System.out.println("findUser");
System.out.println(id);
System.out.println(name);
return "findUser";
}
}
4.6获取请求体中的Json格式参数
- RestFul风格的接口一些比较复杂的参数会转换成Json通过请求体传递过来。这种时候我们可以使用**@RequestBody**注解获取请求体中的数据。
配置:SpringBoot的web启动器已经默认导入了jackson的依赖,不需要再额外导入依赖了。也不需要配置mvc中的注解驱动了。
使用:
2.1 范例一
要求定义个RestFul风格的接口,该接口可以用来新建用户。请求路径要求为 /user ,请求方式要求为POST。
用户数据会转换成json通过请求体传递。
请求体数据:
1
{"name":"三更","age":15}
- 也就是说接收json数据并且转换为对应的对象
2.1.1 获取参数封装成实体对象
- 如果我们想把Json数据获取出来封装User对象,我们可以这样定义方法:
1
2
3
4
5
6
7
8
9
public class UserController {
public String insertUser({ User user)
System.out.println("insertUser");
System.out.println(user);
return "insertUser";
}
}- 实体类
1
2
3
4
5
6
7
8
public class User {
private Integer id;
private String name;
private Integer age;
}2.1.2 获取参数封装成Map集合
- 也可以把该数据获取出来封装成Map集合:
1
2
3
4
5
6
public String insertUser({ Map map)
System.out.println("insertUser");
System.out.println(map);
return "insertUser";
}2.2 范例二
- 如果请求体传递过来的数据是一个User集合转换成的json,Json数据可以这样定义:
1
[{"name":"三更1","age":14},{"name":"三更2","age":15},{"name":"三更3","age":16}]
- 方法定义:
1
2
3
4
5
6@RequestMapping(value = "/users",method = RequestMethod.POST)
public String insertUsers(@RequestBody List<User> users){
System.out.println("insertUsers");
System.out.println(users);
return "insertUser";
}- 注意事项:如果需要使用**@RequestBody**来获取请求体中Json并且进行转换,要求请求头 (key)Content-Type 的值(value)要为: application/json 。
4.7获取QueryString格式参数
- 如果接口的参数是使用QueryString的格式的话,我们也可以使用SpringMVC快速获取参数。
- 我们可以使用**@RequestParam**来获取QueryString格式的参数。
什么是QueryString格式:在请求中间加一个?进行分隔,写
参数名=参数值
,多个参数之间用&进行分隔。如:localhost:8080/user?name=zhangsan&age=18参数单独的获取
- 如果我们想把id,name,likes单独获取出来可以使用如下写法:
- 在方法中定义方法参数,方法参数名要和请求参数名一致,这种情况下我们可以省略**@RequestParam**注解。(SpringMVC自带的功能,详情见JavaWeb复习笔记)
1
2
3
4
5
6
7
8
public String testRquestParam(Integer id, String name, String[] likes){
System.out.println("testRquestParam");
System.out.println(id);
System.out.println(name);
System.out.println(Arrays.toString(likes));
return "testRquestParam";
}1
2注意数组类型的要这么写
localhost:8080/paramstest?name=zhangsan&age=18&likes=唱&likes=跳&likes=rap&likes=篮球- 如果方法参数名和请求参数名不一致,我们可以加上**@RequestParam**注解例如:
1
2
3
4
5
6
7
8
public String testRquestParam({ Integer uid, String name, String[] likes)
System.out.println("testRquestParam");
System.out.println(uid);
System.out.println(name);
System.out.println(Arrays.toString(likes));
return "testRquestParam";
}获取参数封装成实体对象
- 如果我们想把这些参数封装到一个User对象中可以使用如下写法:
1
2
3
4
5
6
public String testRquestParam(User user){
System.out.println("testRquestParam");
System.out.println(user);
return "testRquestParam";
}- User类定义如下:
1
2
3
4
5
6
7
8
9
public class User {
private Integer id;
private String name;
private Integer age;
private String[] likes;
}- 测试时请求url如下:
1
http://localhost:8080/testRquestParam?id=1&name=三更草堂&likes=编程&likes=录课&likes=烫头
注意:实体类中的成员变量要和请求参数名对应上。并且要提供对应的set/get方法。
此时也是SpringMVC的特性自动读取
4.8相关注解其他属性
required:
代表是否必须,默认值为true也就是必须要有对应的参数。如果没有就会报错。
如果对应的参数可传可不传则可以把其设置为fasle
例如:
1
2
3
4
5
6
7
8
public String testRquestParam({ Integer uid, String name, String[] likes)//也就是说这个id属性可传可不传
System.out.println("testRquestParam");
System.out.println(uid);
System.out.println(name);
System.out.println(Arrays.toString(likes));
return "testRquestParam";
}defaultValue:
如果对应的参数没有,我们可以用defaultValue属性设置默认值。
例如:
1
2
3
4
5
6
7
8
public String testRquestParam({ Integer uid, String name, String[] likes)
System.out.println("testRquestParam");
System.out.println(uid);
System.out.println(name);
System.out.println(Arrays.toString(likes));
return "testRquestParam";
}
5.响应体响应数据
- 无论是RestFul风格还是我们之前web阶段接触过的异步请求,都需要把数据转换成Json放入响应体中。springboot还是借助的springmvc框架。
5.1数据放到响应体
- @ResponseBody可以把json放到响应体中。
- @ResponseBody既可以放到类上又可以放到方法上。
- 之前我加在了controller上面,@RestController注解包含@Controller和@ResponseBody。也就是说:现在controller中返回的数据全部会加到响应体里面。
5.2数据转换成json
配置:SpringBoot项目中使用了web的start后,不需要进行额外的依赖和配置。也不需要开启注解驱动。
使用:返回相应的响应数据就行,springmvc会帮我们转换成json数据,放到响应体中。
- 只要把要转换的数据直接作为方法的返回值返回即可。SpringMVC会帮我们把返回值转换成json。具体代码请参考范例。
范例
3.1 范例一
要求定义个RestFul风格的接口,该接口可以用来根据id查询用户。请求路径要求为 /response/user ,请求方式要求为GET。
而请求参数id要写在请求路径上,例如 /response/user/1 这里的1就是id。
要求获取参数id,去查询对应id的用户信息(模拟查询即可,可以选择直接new一个User对象),并且转换成json响应到响应体中。
1
2
3
4
5
6
7
8
9
10
11
public class ResponseController {
//因为返回的数据是要加到响应体当中的,所以加上这个注解。
public User findById({ Integer id)
User user = new User(id, "三更草堂", 15, null);
return user;
}
}
- 注:这里是RestFul风格,也就是查询时
/查询条件
,而不是?隔开查询条件之间写&,所以需要@PathVariable注解告诉mvc你这个请求参数是给谁的。
6.前后端分离案例
- 准备
- 搭建三层架构
- 整合mybatis:引入依赖,配置yml,根据mapper接口生成对应xml文件,写sql语句。
7.接口响应格式统一
我们要保证一个项目中所有接口返回的数据格式的统一。这样无论是前端还是移动端开发获取到我们的数据后都能更方便的进行统一处理。
所以我们定义以下结果封装类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class ResponseResult<T> {
/**
* 状态码
*/
private Integer code;
/**
* 提示信息,如果有错误时,前端可以获取该字段进行提示
*/
private String msg;
/**
* 查询到的结果数据,
*/
private T 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 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, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}之前的接口修改为 :
1
2
3
4
5
public ResponseResult findAll(){
List<User1> users = service.findAll();
return new ResponseResult(200,users);
}
前端版-前端发送请求代码编写
先引入vue
1
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
再引入axios
1
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
两者在这里都是cdn方式引入
编写前端js代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14var v=new vue({
el:"#app",
created(){
this.findAll();
},
methods:{
findAll(){
//请求后台接口把接受到的数据渲染展示在页面中
axios.get("http://localhost/user/findall").then((res)=>{
console.log(res);
})
}
}
})
纯后端:把老师的网页拿过来覆盖一下…
8.跨域请求以及SpringBoot使用CORS解决跨域
什么是跨域:浏览器出于安全的考虑,使用 XMLHttpRequest对象发起 HTTP请求时必须遵守同源策略,否则就是跨域的HTTP请求,默认情况下是被禁止的。 同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。
- 什么叫同源?协议、域名、端口号都完全一致。
这个findall请求(前端) 端口不对。
- 什么叫同源?协议、域名、端口号都完全一致。
CORS解决跨域:
- CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing),允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
- 它通过服务器增加一个特殊的Header[Access-Control-Allow-Origin]来告诉客户端跨域的限制,如果浏览器支持CORS、并且判断Origin通过的话,就会允许XMLHttpRequest发起跨域请求。
SpringBoot使用CORS解决跨域:
可以在支持跨域的方法上或者是Controller上加上@CrossOrigin注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class UserController {
private UserServcie userServcie;
public ResponseResult findAll(){
//调用service查询数据 ,进行返回
List<User> users = userServcie.findAll();
return new ResponseResult(200,users);
}
}使用 WebMvcConfigurer 的 addCorsMappings 方法配置CorsInterceptor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class CorsConfig implements WebMvcConfigurer {
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路径
registry.addMapping("/**")
// 设置允许跨域请求的域名
.allowedOriginPatterns("*")
// 是否允许cookie
.allowCredentials(true)
// 设置允许的请求方式
.allowedMethods("GET", "POST", "DELETE", "PUT")
// 设置允许的header属性
.allowedHeaders("*")
// 跨域允许时间
.maxAge(3600);
}
}
前端版-数据渲染:
前端数据渲染分析:
- 前端收到的是后端返回来的数据:一个对象(ResultResponse),有我们自己设置的状态码code和data数组数据
渲染代码实现:
前端代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23var v = new Vue({
el:"#app",
data:{
users:[]
},
created(){
this.findAll();
},
methods:{
findAll(){
//请求后台接口 把接受到的数据渲染展示在页面中
axios.get("http://localhost/user/findAll").then((res)=>{
console.log(res);
//判断是否成功
if(res.data.code==200){
//如果成功,把数据赋值给this.users
this.users = res.data.data;
}
})
}
}
});为什么统一响应格式?–>在这里方便前端检查错误和接收数据。
9.Token-JWT以及登陆案例
登录校验流程
- 思路分析:在前后端分离的场景中,很多时候会采用token的方案进行登录校验。登录成功时,后端会根据一些用户信息生成一个token字符串返回给前端。前端会存储这个token。以后前端发起请求时如果有token就会把token放在请求头中发送给后端。后端接口就可以获取请求头中的token信息进行解析,如果解析不成功说明token超时了或者不是正确的token,相当于是未登录状态。如果解析成功,说明前端是已经登录过的。
- 因为移动端没有cookie-session,所以token更好,前后端分离登录校验图:
token主流生成方案-JWT
- 本案例采用目前企业中运用比较多的JWT来生成token。cookie-session也能实现
使用时先引入相关依赖
1
2
3
4
5<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>然后可以使用下面的工具类来生成和解析token
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75import 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
1
2
3
4
5
6
7
8
9public static void main(String[] args) throws Exception {
String token = JwtUtil.createJWT(UUID.randomUUID().toString(), "sangeng", null);//可以设置超时时间
System.out.println(token);//生成token
Claims claims = JwtUtil.parseJWT(token);//解析token,如果超时会报错,解析失败也会报错
String subject = claims.getSubject();
System.out.println(subject);
}下一个登录接口逻辑使用jwt方法:
1
String token = JwtUtil.createJWT(UUID.randomUUID().toString(), String.valueOf(loginUser.getId()), null);
uuid生成随机id,获取查询而来的对象id转化为id值,超时时间设置为空—->他们全部转化为一个字符串类型的token对象
```java
Map<String,Object> loginUserTokenMap= new HashMap<>();
loginUserTokenMap.put(“token”,token);1
2
3
4
5
- 下一步将生成的token对象加入创建的map中,最后返回这个map
- ```java
return new ResponseResult(200,"登录成功",loginUserTokenMap);
登录接口实现
数据准备
1
2
3
4
5
6
7
8
9
10
11DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`password` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
/*Data for the table `sys_user` */
insert into `sys_user`(`id`,`username`,`password`) values (1,'root','root'),(2,'sangeng','caotang');实体类
1
2
3
4
5
6
7
8
9
public class SystemUser {
private Integer id;
private String username;
private String password;
}SystemUserController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37import com.sangeng.domain.ResponseResult;
import com.sangeng.domain.SystemUser;
import com.sangeng.service.SystemUserService;
import com.sangeng.utils.JwtUtil;
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;
public class SystemUserController {
private SystemUserService userService;
public ResponseResult login( { SystemUser user)
//校验用户名密码是否正确
SystemUser loginUser = userService.login(user);
Map<String, Object> map;
if (loginUser != null) {
//如果正确 生成token返回
map = new HashMap<>();
String token = JwtUtil.createJWT(UUID.randomUUID().toString(), String.valueOf(loginUser.getId()), null);
map.put("token", token);
} else {
//如果不正确 给出相应的提示
return new ResponseResult(300, "用户名或密码错误,请重新登录");
}
return new ResponseResult(200, "登录成功", map);
}
}Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public interface SystemUserService {
public SystemUser login(SystemUser user);
}
public class SystemUserServcieImpl implements SystemUserService {
private SystemUserMapper systemUserMapper;
public SystemUser login(SystemUser user) {
SystemUser loginUser = systemUserMapper.login(user);
return loginUser;
}
}dao
1
2
3
4
5
public interface UserMapper {
List<User> findAll();
}```xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
3. 登录页面
- 前端准备:在资料中
- 通过异步请求获取
### 10.拦截器
1. 在上个案例来说,如果我们后端多个接口想要对token进行解析判断用户的登录态,如果很多接口都要进行登录校验的话会很麻烦,所以这里使用拦截器更方便。
2. 什么是拦截器?如果我们想在多个Handler方法执行之前或者之后都进行一些处理,甚至某些情况下需要拦截掉,不让Handler方法执行。那么可以使用SpringMVC为我们提供的拦截器。详情见 https://space.bilibili.com/663528522 SpringMVC课程中拦截器相关章节。
3. 拦截器和过滤器要区分开:过滤器是在Servlet执行之前或者之后进行处理。而拦截器是对Handler(处理器)执行前后进行处理。
如图:
4. 使用步骤:
- 创建类实现HandlerInterceptor接口
```java
public class LoginInterceptor implements HandlerInterceptor {
}
实现方法:注意注解的使用,因为springboot不用像ssm那样配置,但是配置换成注解自动注入,注意别忘了注解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class LoginInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取请求头中的token
String token = request.getHeader("token");
//判断token是否为空,如果为空也代表未登录 提醒重新登录(401)
if(!StringUtils.hasText(token)){
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
//解析token看看是否成功
try {
Claims claims = JwtUtil.parseJWT(token);
String subject = claims.getSubject();
System.out.println(subject);
} catch (Exception e) {
e.printStackTrace();
//如果解析过程中没有出现异常说明是登录状态
//如果出现了异常,说明未登录,提醒重新登录(401)
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
return true;
}
}配置拦截器
1
2
3
4
5
6
7
8
9
10
11
12
13
public class LoginConfig implements WebMvcConfigurer {
private LoginInterceptor loginInterceptor;
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)//添加拦截器
.addPathPatterns("/**") //配置拦截路径
.excludePathPatterns("/sys_user/login");//配置排除路径
}
}
11.异常统一处理
非前后端分离:当有错误时,统一跳转到一个页面,体验更好。
前后端分离:当有错误时,后端统一返回json字符串,包括状态码和错误信息。
有两种方法:
- HandlerExceptionResolver:实现接口,重写方法。返回值是ModleAndView类型。但是模板引擎用的多,不是重点不推荐。
- @ControllerAdvice注解:更好。
步骤:
创建类加上@ControllerAdvice注解进行标识
1
2
3
4
public class MyControllerAdvice {
}定义异常处理方法:定义异常处理方法,使用**@ExceptionHandler**标识可以处理的异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyControllerAdvice {
public ResponseResult handlerException(Exception e){
//获取异常信息,存放如ResponseResult的msg属性
String message = e.getMessage();
ResponseResult result = new ResponseResult(300,message);
//把ResponseResult作为返回值返回,要求到时候转换成json存入响应体中
return result;
}
}
12.获取web原生对象
我们之前在web阶段我们经常要使用到request对象,response,session对象等。我们也可以通过SpringMVC获取到这些对象。(不过在MVC中我们很少获取这些对象,因为有更简便的方式,避免了我们使用这些原生对象相对繁琐的API。)
我们只需要在方法上添加对应类型的参数即可,但是注意数据类型不要写错了,SpringMVC会把我们需要的对象传给我们的形参。
注:如果需要获取就这么写。之前是只能这么写,获取request,然后获取它的输出流,然后修改。现在只需要返回一些数据,加上@ResponseBody注解即可。
1
2
3
4
5
6
7
8
9
public class TestController {
public ResponseResult getRequestAndResponse(HttpServletRequest request, HttpServletResponse response, HttpSession session){
System.out.println(request);
return new ResponseResult(200,"成功");
}
}
13.自定义参数解析
解析token,获取解析后包含各种用户信息的对象。例如:在findall接口添加。
1
2
3
4
5
6
7
8
9
10
11
12
13
public ResponseResult findAll(HttpServletRequest request) throws Exception {
//获取请求中的token
String token = request.getHeader("token");
if (StringUtils.hasText(token)){
//如果有的话,解析并且获取用户id
Claims claims = JwtUtil.parseJWT(token);
String userId = claims.getSubject();
System.out.println(userId);
}
List<User1> users = service.findAll();
return new ResponseResult(200,users);
}我们很多接口都需要这种解析获取信息的功能,如果都写就很麻烦,所以这里用到自定义参数解析。
如果我们想实现像获取请求体中的数据那样,在Handler方法的参数上增加一个@RepuestBody注解就可以获取到对应的数据的话。可以使用HandlerMethodArgumentResolver来实现自定义的参数解析。
如果你想直接
findAll(String userId)
来获取,是获取不到的,只有请求参数才是这个方式写,所以为了方便的获取userId,我们要用自定义参数解析。步骤:
定义注解:
1
2
3
4
5
public CurrentUserId {
}创建类实现HandlerMethodArgumentResolver接口并重写其中的方法,注意加上@Component注解注入Spring容器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class UserIdArgumentResolver implements HandlerMethodArgumentResolver{
//判断方法参数使用能使用当前的参数解析器进行解析
public boolean supportsParameter(MethodParameter parameter) {
//如果方法参数有加上CurrentUserId注解,就能把被我们的解析器解析
return parameter.hasParameterAnnotation(CurrentUserId.class);
}
//进行参数解析的方法,可以在方法中获取对应的数据,然后把数据作为返回值返回。方法的返回值就会赋值给对应的方法参数
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;
}
}配置参数解析器:
1
2
3
4
5
6
7
8
9
10
11
public class ArgumentResolverConfig implements WebMvcConfigurer {
private UserIdArgumentResolver userIdArgumentResolver;
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(userIdArgumentResolver);
}
}测试:在需要获取UserId的方法中增加对应的方法参数然后使用@CurrentUserId进行标识即可获取到数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//@CrossOrigin
public class UserController {
private UserServcie userServcie;
public ResponseResult findAll( String userId)throws Exception {
System.out.println(userId);
//调用service查询数据 ,进行返回s
List<User> users = userServcie.findAll();
return new ResponseResult(200,users);
}
}
14.声明式事务
mybatis中有声明式事务依赖
事务是一组操作,要么同时成功要么同时失败。一组中有一个异常就一起回滚。
声明式事务直接在需要事务控制的方法上加上对应的注解**@Transactional**(nb)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class UserServiceImpl implements UserServcie {
private UserMapper userMapper;
public List<User> findAll() {
return userMapper.findAll();
}
public void insertUser() {
//添加2个用户到数据库
User user = new User(null,"sg666",15,"上海");
User user2 = new User(null,"sg777",16,"北京");
userMapper.insertUser(user);
System.out.println(1/0);
userMapper.insertUser(user2);
}
}
15.AOP
p68和p69反了注意一下
AOP:批量地对方法进行增强,没有耦合。一般就是方法调用之前,打印一个日志,调用后打印一个日志来说明调用情况。
AOP详细知识学习见:https://space.bilibili.com/663528522 中的Spring教程。在SpringBoot中默认是开启AOP功能的。如果不想开启AOP功能可以使用如下配置设置为false
1
2
3spring:
aop:
auto: false使用步骤:
添加依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>自定义注解
1
2
3
4
public InvokeLog {
}定义切面类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29//标识这是一个切面类
public class InvokeLogAspect {
//确定切点
public void pt(){
}
public Object printInvokeLog(ProceedingJoinPoint joinPoint){
//目标方法调用前
Object proceed = null;
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String methodName = signature.getMethod().getName();
System.out.println(methodName+"即将被调用");
try {
proceed = joinPoint.proceed();
//目标方法调用后
System.out.println(methodName+"被调用完了");
} catch (Throwable throwable) {
throwable.printStackTrace();
//目标方法出现异常了
System.out.println(methodName+"出现了异常");
}
return proceed;
}
}在需要正确的地方增加对应的注解
1
2
3
4
5
6
7
8
9
10
11
12
public class UserServiceImpl implements UserServcie {
private UserMapper userMapper;
//需要被增强方法需要加上对应的注解
public List<User> findAll() {
return userMapper.findAll();
}
}效果:
切换动态代理:
有的时候我们需要修改AOP的代理方式。我们可以使用以下方式修改:在配置文件中配置spring.aop.proxy-target-class为false这为使用jdk动态代理。该配置默认值为true,代表使用cglib动态代理。
修改主配置文件,切换代理方式:
1
2
3
4
5
6
7
//修改代理方式
public class WebApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(WebApplication.class, args);
}
}如果想生效还需要在配置文件中做如下配置
1
2
3spring:
aop:
proxy-target-class: false #切换动态代理的方式
16.模板引擎相关-Thymeleaf(前后端不分离,用的比较少)
都是访问接口,跳转页面,跳转之前把数据存入域对象当中,然后在模板引擎当中从域对象中获取到相应数据,进行渲染然后展示。(我的项目源码有controller但是没有模板引擎相关的html部分,只是看了视频)
快速入门:
依赖:
1
2
3
4
5<!--thymeleaf依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>定义Controller:在controller中往域中存数据,并且跳转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ThymeleafController {
private UserServcie userServcie;
public String users(Model model){
//获取数据
List<User> users = userServcie.findAll();
//望域中存入数据
model.addAttribute("users",users);
model.addAttribute("msg","hello thymeleaf");
//页面跳转
return "table-standard";
}
}html:在resources\templates下存放模板页面。在html标签中加上 xmlns:th=”http://www.thymeleaf.org"。获取域中的name属性的值可以使用: ${name} 注意要在th开头的属性中使用。
1
2
3<html lang="en" class="no-ie" xmlns:th="http://www.thymeleaf.org">
.....
<div class="panel-heading" th:text="${msg}">Kitchen Sink</div>如果需要引入静态资源,需要使用如下写法。
1
2
3
4
5
6
7
8
9
10
11
12
13<link rel="stylesheet" th:href="@{/app/css/bootstrap.css}">
<!-- Vendor CSS-->
<link rel="stylesheet" th:href="@{/vendor/fontawesome/css/font-awesome.min.css}">
<link rel="stylesheet" th:href="@{/vendor/animo/animate+animo.css}">
<link rel="stylesheet" th:href="@{/vendor/csspinner/csspinner.min.css}">
<!-- START Page Custom CSS-->
<!-- END Page Custom CSS-->
<!-- App CSS-->
<link rel="stylesheet" th:href="@{/app/css/app.css}">
<!-- Modernizr JS Script-->
<script th:src="@{/vendor/modernizr/modernizr.js}" type="application/javascript"></script>
<!-- FastClick for mobiles-->
<script th:src="@{/vendor/fastclick/fastclick.js}" type="application/javascript"></script>遍历语法:遍历的语法 th:each=”自定义的元素变量名称 : ${集合变量名称}”
1
2
3
4
5
6<tr th:each="user:${users}">
<td th:text="${user.id}"></td>
<td th:text="${user.username}"></td>
<td th:text="${user.age}"></td>
<td th:text="${user.address}"></td>
</tr>
17.整合Redis
依赖:
1
2
3
4
5<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>配置Redis地址和端口号
1
2
3
4spring:
redis:
host: 127.0.0.1 #redis服务器ip地址
port: 6379 #redis端口号注入RedisTemplate使用
1
2
3
4
5
6
7
private StringRedisTemplate redisTemplate;
public void testRedis(){
redisTemplate.opsForValue().set("name","三更");
}
18.环境切换
- 为什么使用profile:
- 在实际开发环境中,我们存在开发环境的配置,部署环境的配置,测试环境的配置等等,里面的配置信息很多时,例如:端口、上下文路径、数据库配置等等,若每次切换环境时,我们都需要进行修改这些配置信息时,会比较麻烦,profile的出现就是为了解决这个问题。它可以让我们针对不同的环境进行不同的配置,然后可以通过激活、指定参数等方式快速切换环境。
- 使用:
- 创建profile配置文件
- 我们可以用application-xxx.yml的命名方式 创建配置文件,其中xxx可以根据自己的需求来定义。
- 例如:我们需要一个测试环境的配置文件,则可以命名为:application-test.yml。需要一个生产环境的配置文件,可以命名为:application-prod.yml。我们可以不同环境下不同的配置放到对应的profile文件中进行配置。然后把不同环境下都相同的配置放到application.yml文件中配置。
- 激活环境:
- 我们可以再application.yml文件中使用spring.profiles.active属性来配置激活哪个环境。
- 也可以使用虚拟机参数来指定激活环境。例如 : -Dspring.profiles.active=test
- 也可以使用命令行参数来激活环境(打包:注意先引入打包插件,然后cmd:java -jar 文件名 –spring.profiles.active =test,最后等于号后面的环境可以改)。例如: –spring.profiles.active =test
19.日志
开启日志:
1
2
3
4debug: true #开启日志
logging:
level:
com.sangeng: debug #设置日志级别
20.指标监控
我们在日常开发中需要对程序内部的运行情况进行监控, 比如:健康度、运行指标、日志信息、线程状况等等 。而SpringBoot的监控Actuator就可以帮我们解决这些问题。
使用:
添加依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>访问监控接口:http://localhost:81/actuator
配置启用监控端点:
1
2
3
4
5
6management:
endpoints:
enabled-by-default: true #配置启用所有端点
web:
exposure:
include: "*" #web端暴露所有端点
常用端点:
端点名称 描述 beans
显示应用程序中所有Spring Bean的完整列表。 health
显示应用程序运行状况信息。 info
显示应用程序信息。 loggers
显示和修改应用程序中日志的配置。 metrics
显示当前应用程序的“指标”信息。 mappings
显示所有 @RequestMapping
路径列表。scheduledtasks
显示应用程序中的计划任务。
- 图形化界面 SpringBoot Admin:
创建SpringBoot Admin Server应用,要求引入spring-boot-admin-starter-server依赖
1
2
3
4<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>然后在启动类上加上@EnableAdminServer注解
配置SpringBoot Admin client应用:在需要监控的应用中加上spring-boot-admin-starter-client依赖
1
2
3
4
5<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.3.1</version>
</dependency>然后配置SpringBoot Admin Server的地址
1
2
3
4
5spring:
boot:
admin:
client:
url: http://localhost:8888 #配置 Admin Server的地址启动,访问url。