服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|编程技术|正则表达式|

服务器之家 - 编程语言 - JAVA教程 - Spring Boot(五)之跨域、自定义查询及分页

Spring Boot(五)之跨域、自定义查询及分页

2020-09-18 14:35Java之家 JAVA教程

这篇文章主要介绍了Spring Boot(五)之跨域、自定义查询及分页的的相关资料,需要的朋友可以参考下

跨域

前面我们初步做出了一个可以实现受保护的 rest api,但是我们没有涉及一个前端领域很重要的问题,那就是跨域请求( cross-origin http request )。先来回顾一些背景知识:

跨域请求

定义:当我们从本身站点请求不同域名或端口的服务所提供的资源时,就会发起跨域请求。

例如最常见的我们很多的 css 样式文件是会链接到某个公共 cdn 服务器上,而不是在本身的服务器上,这其实就是典型的一个跨域请求。但浏览器由于安全原因限制了在脚本( script )中发起的跨域 http 请求。也就是说 xmlhttprequest 和 fetch 等是遵循“同源规则”的,即只能访问自己服务器的指定端口的资源(同一服务器不同端口也会视为跨域)。但这种限制在今天,我们的应用需要访问多种外部 api 或 资源的时候就不能满足开发者的需求了,因此就产生了若干对于跨域的解决方案,jsonp 是其中一种,但在今天来看主流的更彻底的解决方案是 cors ( cross-origin resource sharing )。

跨域资源共享 ( cors )

这种机制将跨域的访问控制权交给服务器,这样可以保证安全的跨域数据传输。现代浏览器一般会将 cors 的支持封装在 http api 之中( 比如 xmlhttprequest 和 fetch ),这样可以有效控制使用跨域请求的风险,因为你绕不过去,总得要使用 api 吧。

概括来说,这个机制是增加一系列的 http 头来让服务器可以描述哪些源是允许使用浏览器来访问资源的。而且对于简单的请求和复杂请求,处理机制是不一样的。

简单请求仅允许三个 http 方法:get,post 以及 head,另外只能支持若干 header 参数:accept , accept-language , content-language , content-type (值只能是 application/x-www-form-urlencoded、multipart/form-data 和 text/plain), dpr , downlink , save-data , viewport-width 和 width。

对于简单请求来说,比如下面这样一个简单的get请求:从http://me.domain发起到http://another.domain/data/blablabla的资源请求

?
1
2
3
4
5
get /data/blablabla/ http/1.1
// 请求的域名
host: another.domain
...//省略其它部分,重点是下面这句,说明了发起请求者的来源
origin: http://me.domain

应用了 cors 的对方服务器返回的响应应该像下面这个样子,当然这里 access-control-allow-origin: * 中的 * 表示任何网站都可以访问该资源,如果要限制只能从 me.domain 访问,那么需要改成 access-control-allow-origin:http://me.domain

?
1
2
3
4
5
http/1.1 200 ok
...//省略其它部分
access-control-allow-origin: *
...//省略其它部分
content-type: application/json

那么对于复杂请求怎么办呢?这需要一次预检请求和一次实际的请求,也就是说需要两次和对方服务器的请求/响应。预检请求是以 option 方法进行的,因为 option 方法不会改变任何资源,所以这个预检请求是安全的,它的职责在于发送实际请求将会使用的 http 方法以及将要发送的 header 中将携带哪些内容,这样对方服务器可以根据预检请求的信息决定是否接受。

?
1
2
3
4
5
6
7
// 预检请求
options /resources/post/ http/1.1
host: another.domain
...// 省略其它部分
origin: http://me.domain
access-control-request-method: post
access-control-request-headers: content-type

服务器对预检请求的响应如下:

?
1
2
3
4
5
6
7
8
http/1.1 200 ok
// 省略其它部分
access-control-allow-origin: http://me.domain
access-control-allow-methods: post, get, options
access-control-allow-headers: content-type
access-control-max-age: 86400
// 省略其它部分
content-type: text/plain

接下来的正式请求就和上面的简单请求差不多了,就不赘述了。

在 spring boot 中如何启用 cors

啰嗦了这么多,终于进入正题,但我一直觉得不能光知其然而不知其所以然,所以各位就忍了吧。加入 cors 的支持在 spring boot 中简单到不忍直视,添加一个配置类即可:

?
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
import org.springframework.boot.web.servlet.filterregistrationbean;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.web.cors.corsconfiguration;
import org.springframework.web.cors.urlbasedcorsconfigurationsource;
import org.springframework.web.filter.corsfilter;
@configuration
public class corsconfig {
 @bean
 public filterregistrationbean corsfilter() {
  urlbasedcorsconfigurationsource source = new urlbasedcorsconfigurationsource();
  corsconfiguration config = new corsconfiguration();
  config.setallowcredentials(true);
  // 设置你要允许的网站域名,如果全允许则设为 *
  config.addallowedorigin("http://localhost:4200");
  // 如果要限制 header 或 method 请自行更改
  config.addallowedheader("*");
  config.addallowedmethod("*");
  source.registercorsconfiguration("/**", config);
  filterregistrationbean bean = new filterregistrationbean(new corsfilter(source));
  // 这个顺序很重要哦,为避免麻烦请设置在最前
  bean.setorder(0);
  return bean;
 }
}

如果我们使用 postman 访问一下 api,会发现得到一个 invalid cors request 的响应,因为我们的 api 只授权给了 localhost:4200

Spring Boot(五)之跨域、自定义查询及分页

用 postman 无法得到请求结果

当然,如果我们使用 curl 的话是可以访问的,这是因为 curl 不是浏览器。

嗯嗯,这样就结束了,这节好水,但就是这么简单啊。

自定义查询

我们回过头来再来看看数据查询,大部分情况下 spring data 提供的按方法名进行查询的方式足够简单也足够强大,但总归还是有很多局限。为了说明这个问题,也顺便为我的新 angular 项目打造一个 api,我们把 api 的需求改一下。现在我们要做的不是一个简单的 todo 了,而是类似 teambition 或 worktile 那样的企业协作平台,当然我们不会做的那么复杂,是个简化版本。那么这时我们的对象模型是这样的:

Spring Boot(五)之跨域、自定义查询及分页

主要的领域对象

我们首先看一下 project 这个对象,我们来构建它的 api,增删改没啥可讲。但是查询上会有点不一样,首先我们并不希望把所有的 project 都查出来,而是该用户参与的项目要提供一个 api 给客户端。

project 和 user 按关系型数据库的看法是个多对多的关系,在mongodb中这其实有多种做法,可以在 user 对象中设置一个 project 的集合属性,也可以在 project 中设置 user 的集合属性 (在我们的例子里是 memebers ),还可以两者结合,就是在 user 和 projet 中互相有对方的集合属性。具体采用哪种需要看业务场景和性能需求,比如如果任何一个项目的成员数如果不会很大,那么在 project 中嵌入 user 集合就比较划算;如果项目的成员较多,但一个成员归属的项目不会很多的情况下,就可以把 project 的集合嵌入到 user 中。我们这里采用了第一种做法。

那么接下来我们来写该用户参与的项目的查询。当然我们可以按照 spring data 强大的按方法名称来生成对应查询的方式来做:寻找 members 属性中包含该用户的集合

?
1
set<project> findbymemberscontaining(user user)

看起来还可以,挺简单,但是如果我们说再加两个条件要筛选 project.enabled == true (我们不会物理删除项目,而是设置其标志位 enabled,所以这就是筛选未删除的项目) 和 project.archived == false (项目完结后需要归档,这就是筛选未归档的)。这两个条件一加上,好家伙,我们的方法名变成了下面这个样子,不忍直视啊:

?
1
set<project> findbymemberscontainingandenabledandarchived(user user, boolean enabled, boolean archived)

当然好用还是好用了,但是这个方法名也太长了,好在 spring data 中提供很多种方式自定义查询,我们介绍一种相对简单的:利用 @query 注解来进行查询,方法名字就没有那么雷人了:

?
1
2
@query("{'owner.$id': ?#{[0]}, 'enabled': ?#{[1]}, 'archived': ?#{[2]}}")
set<project> findrelated(user user, boolean enabled, boolean archived)

这个注解中的内容是一个 json 对象,就和我们在 mongodb 的控制台查询的find()中的内容是一样的,只不过将双引号换成单引号,将需要变量用 [0]、[1] 和 [2] 的形式表示第一、第二和第三个参数。那么 ?#{} 是表示里面的内容是个 spel ( spring 的表达式语言) 表达式。

所以实践中,我们可以在 mongodb 的控制台去实验语句是否好用,然后在 spring 中编写表达式。

?
1
2
3
4
5
6
db.project.find(
 {
  "owner.$id": objectid("58f5a904edc76ab0e033cfc3"), 
  "enabled": true,
  "archived": false
 })

Spring Boot(五)之跨域、自定义查询及分页

在mongodb的console查询

数据的分页

很多时候我们希望 api 返回的数据是可以分页的,这个分页问题在 spring boot 有怎样便捷的方法呢?我们是否需要再定义一堆什么 pagesize,pagecount,start, off 的参数呢?答案是完全没必要,分页这个事情对于 spring boot 来说很简单,只需改变各层级原有方法中返回的 list 或 set 对象为 page 对象,传入参数多一个 pageable 类型的参数即可。

?
1
2
3
4
5
6
7
8
9
10
11
12
// controller
@requestmapping(method = requestmethod.get)
public page<project> findrelated(
  @requestheader(value = "userid") string userid,
  @requestparam(value = "enabled", defaultvalue = "true", required = false) boolean enabled,
  @requestparam(value = "archived", defaultvalue = "false", required = false) boolean archived,
  pageable pageable) {
 return service.findrelated(userid, enabled, archived, pageable);
}
// repository
@query("{'owner.$id': ?#{[0]}, 'enabled': ?#{[1]}, 'archived': ?#{[2]}}")
page<project> findrelated(objectid userid, boolean enabled, boolean archived, pageable pageable);

现在呢,我们就可以这样使用了 get http://localhost:8090/projects/?page=0&size=3 表示取每页三个数据取第一页。

本章代码:https://github.com/wpcfan/spring-boot-tut/tree/chap05

原文链接:https://juejin.im/post/58f9abdf570c350058cb3856

延伸 · 阅读

精彩推荐