0%

SpringBoot2.x(二)SpringBoot接口Http

HTTP配置详解

@RestController

@RestController继承了@Controller@ResponseBody,该注解可同时发挥这两个注解的作用,@ResponseBody能将 @RequestMapping标注的方法的返回值以json的形式响应给浏览器

@SpringApplication

@SpringApplication继承了 @ComponentScan@SpringBootConfiguration和@EnableAutoConfiguration@ComponentScan的扫描规则是被 @SpringBootConfiguration注解直接或间接标注的类所在的包及其子包。

@EnableAutoConfiguration的作用则是根据项目引入的依赖自动配置相关技术所需的配置项(省去了原来的applicationContext-xxx.xml


PostMan接口调试工具的介绍和使用

PostMan是一款可以进行http接口调试和记录保存的http接口开发工具。他可以帮我们测试接口的功能性,同时我们可以将测试过的接口按模块划分保存下来,方便接口文档的编写和与测试人员的交接。

下载地址:https://www.getpostman.com/

测试接口

保存记录

创建分类文件夹(Collection)分类保存接口记录

一般按照接口所属的模块来创建Collection分类保存接口记录

将接口记录保存到Collection中

new tab填写url,点击save

url命名请求名称,并保存到Collection

更多PostMan功能可自行百度学习。


HTTP接口GET请求实战

@GetMapping

如果我们要限制某接口的提交方式,常见是这样:

1
2
3
4
5
6
7
8
private Map params = new HashMap();
@RequestMapping(path = "/getMapping/{city_id}/{user_id}",method = RequestMethod.GET)
public Object getParmas(@PathVariable("city_id") String cityId, @PathVariable("user_id") String userId) {
params.clear();
params.put("cityId",cityId);
params.put("userId", userId);
return params;
}

其实我们可以通过 @GetMapping简化此步:

1
2
3
4
5
6
7
@GetMapping("/getMapping/{city_id}/{user_id}")
public Object getParmas(@PathVariable("city_id") String cityId, @PathVariable("user_id") String userId) {
params.clear();
params.put("cityId",cityId);
params.put("userId", userId);
return params;
}

接口为什么用下划线而不用驼峰?因为有些语言不支持驼峰,为了确保接口的通用性,一般同一用小写+下划线

如果我们使用 PostMan 测试该接口:localhost:8080/getMapping/100/2

1
2
3
4
5
6
7
8
9
10
11
12
13
POST:
{
"timestamp": "2018-07-16T14:30:47.244+0000",
"status": 405,
"error": "Method Not Allowed",
"message": "Request method 'POST' not supported",
"path": "/getMapping/100/20"
}
GET:
{
"cityId": "100",
"userId": "20"
}

类似的还有 @PostMapping,@PutMapping,@DeleteMapping

@RequestParam

在请求发送的参数和controller中的方法参数之间做转换

1
2
3
4
5
6
@GetMapping("/test2")
public Object getParams(@RequestParam(defaultValue = "1", name = "page") String param1, String param2) {
params.put("param1", param1);
params.put("param2", param2);
return params;
}

你可以通过 defaultValue为参数附加一个默认值,防止空值可能带来的异常,可以通过 name将请求的参数名映射到方法形参的参数名上。

@RequestBody

编写 pojo

1
2
3
4
5
6
7
8
9
package top.zhenganwen.springbootweb.pojo;

import lombok.Data;

@Data
public class User {
private String username;
private String password;
}

关于 lombok插件可以简洁的实现一个pojo类,用法可参见https://blog.csdn.net/motui/article/details/79012846,IDEA需先安装此插件并引入依赖:

1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>

如果pom文件报错:failed to read artifact descriptor for xx.jar则是由于相关依赖没有下载成功,需要更改 mavensettting.xml,将其中的 mirror改为访问正常且涵盖依赖较多的镜像,如我的是:

1
2
3
4
5
6
7
8
9
10
11
<mirrors>
<!-- mirror
Specifies a repository mirror site to use instead of a given repository. The repository that this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
-->
<mirror>
   <id>repo2</id>
   <mirrorOf>central</mirrorOf>
   <name>Human Readable Name for this Mirror.</name>
   <url>http://repo2.maven.org/maven2/</url>
</mirror>
</mirrors>

接口

1
2
3
4
5
6
@PostMapping("/test3")
public Object test3(@RequestBody User user) {
params.put("username", user.getUsername());
params.put("password", user.getPassword());
return params;
}

值得注意的是只有POST提交才可附加请求头而GET提交没有请求头。

这里通过 @RequestBody可设置请求头的 Content-Typeapplication/json

使用PostMan测试一下:

@RequestHeader获取头信息

有时我们会将系统同行令牌附加在头信息中,在请求中获取令牌进行验证

1
2
3
4
5
6
 @RequestMapping("/test4")
public Object test4(@RequestHeader("access_token") String accessToken,String id) {
params.put("accessToken", accessToken);
params.put("id", id);
return params;
}

HttpServletRequest自动注入

ControllerHttpServletRequest 类型的成员变量或方法形参会被自动注入当前的请求对象。

HTTP接口POST,PUT,DELETE请求实战

POST,PUT,DELETE提交性质与GET相同,只不过有这不同的语义:GET一般是查询,PUT一般是更新,DELETE一般是删除。


常用json框架介绍和Jackson返回结果处理

常用框架 阿里 fastjson,谷歌gson等

性能比较

JavaBean序列化为Json,性能:Jackson > FastJson > Gson > Json-lib (同个结构的条件下)

Jackson、FastJson、Gson类库各有优点,各有自己的专长。但无非就是空间换时间、时间换空间

@JsonIgnore指定不返回

  • Domain
1
2
3
4
5
6
@Data
@AllArgsConstructor
public class User {
private String username;
private String password;
}
  • Controller
1
2
3
4
5
@GetMapping("/test5")
public Object test5() {
User user = new User("tom", "123456"); //模拟查询用户
return user;
}
  • 接口测试
1
2
3
4
{
"username": "tom",
"password": "123456"
}

你会发现密码被带回到了前端,这并不利与用户安全。因此我们可以在此字段上标注 @JsonIgnore

1
2
3
4
5
6
7
@Data
@AllArgsConstructor
public class User {
private String username;
@JsonIgnore
private String password;
}

再次测试你会发现此字段已被过滤了(javabean转json时忽略了该字段)。

@JsonFormat指定日期格式

1
2
3
4
5
6
7
8
9
@Data
@AllArgsConstructor
public class User {
private String username;
@JsonIgnore
private String password;
@JsonFormat(pattern="yyyy-MM-dd hh:mm:ss",locale="zh",timezone="GMT+8")
private Date createdTime;
}
1
2
3
4
{
"username": "tom",
"createdTime": "2018-07-17 12:13:21"
}

@JsonInclude空字段不返回

如果你不想返回的json中带有null值的字段:

1
2
3
4
5
6
7
private String username;
@JsonIgnore
private String password;
@JsonFormat(pattern="yyyy-MM-dd hh:mm:ss",locale="zh",timezone="GMT+8")
private Date createdTime;

private String phone;
1
2
3
4
5
@GetMapping("/test5")
public Object test5() {
User user = new User("tom", "123456",new Date(),null);
return user;
}
1
2
3
4
5
{
"username": "tom",
"createdTime": "2018-07-17 12:16:28",
"phone": null
}

你可以添加此标注

1
2
@JsonInclude(JsonInclude.Include.NON_NULL)
private String phone;

接口测试:

1
2
3
4
{
"username": "tom",
"createdTime": "2018-07-17 12:16:28"
}

@JsonProperty指定别名

有时攻击者会通过接口返回的json推敲数据库表字段和表结构以发现漏洞,这时我们需要伪装一下字段名称:

1
2
3
@JsonProperty("account")
@JsonInclude(JsonInclude.Include.NON_NULL)
private String phone;
1
2
3
4
5
@GetMapping("/test5")
public Object test5() {
User user = new User("tom", "123456",new Date(),"165435615");
return user;
}

接口测试:

1
2
3
4
5
{
"username": "tom",
"createdTime": "2018-07-17 01:52:37",
"account": "165435615"
}

SpringBoot2.x目录结构详解

maven目录结构

如果基于maven构建SpringBoot2.x,则 src/main/java用来存放代码, src/main/resources则用来存放静态资源和配置文件

resources下目录规则

  • static: 存放静态文件,比如 cssjsimage, (访问方式如 http://localhost:8080/js/main.js)
  • templates:存放静态页面jsp,html,tpl
  • config:存放配置文件
  • resources: 存放其他资源文件
  • public:存放可公开访问的文件

资源文件的查找顺序

Spring Boot 默认会挨个从META/resourcesMETAWEB-INF同级)> resources > static > public 里面找是否存在相应的资源,如果有则直接返回。

如果你的目录结构如下

  • src/main/resources
    • resources
      • resources.xml
    • static
      • static.html
    • public
      • public.js

访问如下路径均能找到:

Thymeleaf

如果在 resources/templates中创建 index.html,通过 localhost:8080/index.html是无法访问到的,此时我们需要引入 Thymeleaf依赖并通过 Controller映射:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
1
2
3
4
@RequestMapping("/test6")
public String test6() {
return "index";
}

通过 localhost:8080/test6便可访问到 resources/templates/index.html

自定义要加载的静态内容

基于SpringBoot2.x默认只会加载 META/resourcesresources/resourcesresources/publicresources/static中的文件,我们可能需要一些自定义的配置

如我们需要 resources/test/test.js能被直接访问到该怎么办?

可以在SpringBoot的全局配置文件resources/application.properties中添加如下配置:

1
spring.resources.static-locations = classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/test/

这样就可以保留约定俗成的,添加自定义的,加载顺序为以逗号分隔的顺序

访问 localhost:8080/test.js试试。

值得注意的是,高并发场景下servlet容器对静态资源并不友好,一般使用SpringBoot做前后端分离,前端的静态资源文件在页面中通过CDN加载,而不会放到SpringBoot中。


文件上传实战

静态页面直接访问

如果想要直接访问静态文件,需要将其放在springboot默认加载的文件夹下。

UploadController

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
package top.zhenganwen.springbootweb.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import top.zhenganwen.springbootweb.entity.JsonResult;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.UUID;

/**
* UploadController class
*
* @author zhenganwen
* @date 2018/7/17
*/
@RestController
@RequestMapping("upload")
public class UploadController {

@Autowired
private HttpServletRequest request;

@RequestMapping("/pic")
public JsonResult uploadPic(@RequestParam(name = "pic") MultipartFile file){

//校验文件是否为空,上传大小
...

String imgSavePath = ClassLoader.getSystemClassLoader().getResource("resources/images").getPath()+File.separator;

//获取文件扩展名
String originalFilename = file.getOriginalFilename();
String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);

//生成唯一文件名
String fileName = UUID.randomUUID() + "." + extName;


try {
//保存
//transferTo方法比自己用FileOutputStream便捷且更加高效
file.transferTo(new File(imgSavePath + fileName));
//返回图片路径
return new JsonResult(0, "success", "/images/" + fileName);
} catch (IOException e) {
e.printStackTrace();
return new JsonResult(-1, "fail to upload file");
}



}
}

JsonResult

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
package top.zhenganwen.springbootweb.entity;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.Serializable;

@Data
@AllArgsConstructor
public class JsonResult implements Serializable {

/**
* 响应状态码
* 0表示操作成功
* -1表示操作失败
*/
private int code;
private String message;
private Object data;

public JsonResult(int code, String message) {
this.code = code;
this.message = message;
}

public JsonResult(int code, String message, Object data) {
this.code = code;
this.data = data;
}
}

upload.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>上传文件</title>
</head>
<body>
<form action="/upload/pic" method="post" enctype="multipart/form-data">
点击选择文件:<input type="file" name="pic">
<input type="submit" value="上传">
</form>
</body>
</html>

上传测试

1
2
3
4
5
{
"code":0,
"message": "success",
"data":"/images/f3a22bfd-3b7b-436f-82af-d46cfc4771ff.jpg"
}

访问图片:

1
localhost:8080/images/f3a22bfd-3b7b-436f-82af-d46cfc4771ff.jpg

上传大小的限制

SpringBoot默认将单个文件的上传大小限制为500KB左右(1048576 bytes),如果你需要自定义则需在有 @Configuration标注的类中添加如下配置:

1
2
3
4
5
6
7
8
9
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
//单个文件上传最大限制,字符串单位可选KB/B,数值单位为B
factory.setMaxFileSize("10240KB");
//上传数据总大小限制
factory.setMaxRequestSize("102400KB");
return factory.createMultipartConfig();
}

jar包方式运行web项目的文件上传和访问处理

jar包运行

当我们将maven+springboot构建的web项目 install之后,试图运行 target中的 jar文件时会报错找不到启动类

1
F:\IDEA_project\springboot-web\target>java -jar springboot-web-0.0.1-SNAPSHOT.jar

这时你需要添加一个项目构建的插件:

1
2
3
4
5
6
7
8
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

你可以解压 springboot-web-0.0.1-SNAPSHOT.jar,查看其中的 META-INF/MANIFEST.MF

1
2
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: top.zhenganwen.springbootweb.SpringbootWebApplication

这里就为我们指定了应用程序的入口

jar包运行web应用上传图片到服务器

如果我们通过jar包运行web应用,那么我们上传的图片势必只能存放在jar包之外,那么如果通过该web应用访问图片呢?

首先将上传的图片保存到服务器的某个文件夹下:

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
@RequestMapping("/pic")
public JsonResult uploadPic(@RequestParam(name = "pic") MultipartFile file){

//String imgSavePath = ClassLoader.getSystemClassLoader().getResource("resources/images").getPath()+File.separator;

String imgSavePath = "C:/Users/zaw/Desktop/";

//获取文件扩展名
String originalFilename = file.getOriginalFilename();
String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);

//生成唯一文件名
String fileName = UUID.randomUUID() + "." + extName;


try {
//保存
//transferTo方法比自己用FileOutputStream便捷且更加高效
file.transferTo(new File(imgSavePath + fileName));
//返回图片路径
return new JsonResult(0, fileName);
} catch (IOException e) {
e.printStackTrace();
return new JsonResult(-1, "fail to upload file");
}

}

其次,我们需要在application.properties中将该文件夹添加到SpringBoot加载的路径中:

1
2
web.image-path=C:/Users/zaw/Desktop 	#web.image-path为变量名,名称任意
spring.resources.static-locations = classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,file:${web.image-path}

这样我们就能通过 http://localhost:8080/f09d2c03-d6f6-4618-99ea-3c58a2a193e0.jpg的形式访问到我们上传的图片了

文件服务器

在网站访问量较高时,我们需要将图片等静态资源文件与应用程序分离开来。常用的解决方案有FastDFS、阿里云ossnginx简单文件服务器等。

鼓励一下~