重拾后端之Spring Boot(五) -- 跨域、自定义查询及分页

重拾后端之Spring Boot(一):REST API的搭建能够这样简单
重拾后端之Spring Boot(二):MongoDb的无缝集成
重拾后端之Spring Boot(三):找回熟悉的Controller,Service
重拾后端之Spring Boot(四):使用 JWT 和 Spring Security 保护 REST API
重拾后端之 Spring Boot(五) -- 跨域、自定义查询及分页
juejin.im/post/58f9ab…
重拾后端之Spring Boot(六) -- 热加载、容器和多项目css

跨域

前面咱们初步作出了一个能够实现受保护的 REST API,可是咱们没有涉及一个前端领域很重要的问题,那就是跨域请求( cross-origin HTTP request )。先来回顾一些背景知识:html

跨域请求

定义:当咱们从自己站点请求不一样域名或端口的服务所提供的资源时,就会发起跨域请求。前端

例如最多见的咱们不少的 css 样式文件是会连接到某个公共 CDN 服务器上,而不是在自己的服务器上,这其实就是典型的一个跨域请求。但浏览器因为安全缘由限制了在脚本( script )中发起的跨域 HTTP 请求。也就是说 XMLHttpRequestFetch 等是遵循“同源规则”的,即只能访问本身服务器的指定端口的资源(同一服务器不一样端口也会视为跨域)。但这种限制在今天,咱们的应用须要访问多种外部 API 或 资源的时候就不能知足开发者的需求了,所以就产生了若干对于跨域的解决方案,JSONP 是其中一种,但在今天来看主流的更完全的解决方案是 CORS ( Cross-Origin Resource Sharing )。java

跨域资源共享 ( CORS )

这种机制将跨域的访问控制权交给服务器,这样能够保证安全的跨域数据传输。现代浏览器通常会将 CORS 的支持封装在 HTTP API 之中( 好比 XMLHttpRequestFetch ),这样能够有效控制使用跨域请求的风险,由于你绕不过去,总得要使用 API 吧。git

归纳来讲,这个机制是增长一系列的 HTTP 头来让服务器能够描述哪些源是容许使用浏览器来访问资源的。并且对于简单的请求和复杂请求,处理机制是不同的。github

简单请求仅容许三个 HTTP 方法:GET,POST 以及 HEAD,另外只能支持若干 header 参数:Accept , Accept-Language , Content-Language , Content-Type (值只能是 application/x-www-form-urlencodedmultipart/form-datatext/plain), DPR , Downlink , Save-Data , Viewport-Width 和 Width。web

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

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数据库

HTTP/1.1 200 OK
...//省略其它部分
Access-Control-Allow-Origin: *
...//省略其它部分
Content-Type: application/json复制代码

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

// 预检请求
OPTIONS /resources/post/ HTTP/1.1
Host: another.domain
...// 省略其它部分
Origin: http://me.domain
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type复制代码

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

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 中简单到不忍直视,添加一个配置类便可:

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

用 POSTMAN 没法获得请求结果
用 POSTMAN 没法获得请求结果

固然,若是咱们使用 CURL 的话是能够访问的,这是由于 CURL 不是浏览器。

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

自定义查询

咱们回过头来再来看看数据查询,大部分状况下 Spring Data 提供的按方法名进行查询的方式足够简单也足够强大,但总归仍是有不少局限。为了说明这个问题,也顺便为个人新 Angular 项目打造一个 API,咱们把 API 的需求改一下。如今咱们要作的不是一个简单的 Todo 了,而是相似 Teambition 或 Worktile 那样的企业协做平台,固然咱们不会作的那么复杂,是个简化版本。那么这时咱们的对象模型是这样的:

主要的领域对象
主要的领域对象

咱们首先看一下 Project 这个对象,咱们来构建它的 API,增删改没啥可讲。可是查询上会有点不同,首先咱们并不但愿把全部的 Project 都查出来,而是该用户参与的项目要提供一个 API 给客户端。

Project 和 User 按关系型数据库的见解是个多对多的关系,在MongoDB中这其实有多种作法,能够在 User 对象中设置一个 Project 的集合属性,也能够在 Project 中设置 User 的集合属性 (在咱们的例子里是 memebers ),还能够二者结合,就是在 User 和 Projet 中互相有对方的集合属性。具体采用哪一种须要看业务场景和性能需求,好比若是任何一个项目的成员数若是不会很大,那么在 Project 中嵌入 User 集合就比较划算;若是项目的成员较多,但一个成员归属的项目不会不少的状况下,就能够把 Project 的集合嵌入到 User 中。咱们这里采用了第一种作法。

那么接下来咱们来写该用户参与的项目的查询。固然咱们能够按照 Spring Data 强大的按方法名称来生成对应查询的方式来作:寻找 members 属性中包含该用户的集合

Set<Project> findByMembersContaining(User user)复制代码

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

Set<Project> findByMembersContainingAndEnabledAndArchived(User user, boolean enabled, boolean archived)复制代码

固然好用仍是好用了,可是这个方法名也太长了,好在 Spring Data 中提供不少种方式自定义查询,咱们介绍一种相对简单的:利用 @Query 注解来进行查询,方法名字就没有那么雷人了:

@Query("{'owner.$id': ?#{[0]}, 'enabled': ?#{[1]}, 'archived': ?#{[2]}}")
Set<Project> findRelated(User user, boolean enabled, boolean archived)复制代码

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

因此实践中,咱们能够在 MongoDB 的控制台去实验语句是否好用,而后在 Spring 中编写表达式。

db.project.find(
    {
        "owner.$id": ObjectId("58f5a904edc76ab0e033cfc3"),        
        "enabled": true, 
        "archived": false
    })复制代码

在MongoDB的console查询
在MongoDB的console查询

数据的分页

不少时候咱们但愿 API 返回的数据是能够分页的,这个分页问题在 Spring Boot 有怎样便捷的方法呢?咱们是否须要再定义一堆什么 pageSize,pageCount,start, off 的参数呢?答案是彻底不必,分页这个事情对于 Spring Boot 来讲很简单,只需改变各层级原有方法中返回的 List 或 Set 对象为 Page 对象,传入参数多一个 Pageable 类型的参数便可。

// Controller
@RequestMapping(method = RequestMethod.GET)
public Page
  
  
  

 
  
  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 
 
  
    findRelated(ObjectId userId, boolean enabled, boolean archived, Pageable pageable); 
   

 复制代码

如今呢,咱们就能够这样使用了 GET http://localhost:8090/projects/?page=0&size=3 表示取每页三个数据取第一页。

本章代码:github.com/wpcfan/spri…

另外,个人 《Angular 从零到一》出版了,下面是书籍的内容简介:

本书系统介绍Angular的基础知识与开发技巧,可帮助前端开发者快速入门。共有9章,第1章介绍Angular的基本概念,第2~7章从零开始搭建一个待办事项应用,而后逐步增长功能,如增长登陆验证、将应用模块化、多用户版本的实现、使用第三方样式库、动态效果制做等。第8章介绍响应式编程的概念和Rx在Angular中的应用。第9章介绍在React中很是流行的Redux状态管理机制,这种机制的引入可让代码和逻辑隔离得更好,在团队工做中强烈建议采用这种方案。本书不只讲解Angular的基本概念和最佳实践,并且分享了做者解决问题的过程和逻辑,讲解细腻,风趣幽默,适合有面向对象编程基础的读者阅读。

欢迎你们围观、订购、提出宝贵意见。

京东连接:item.m.jd.com/product/120…

Angular从零到一
Angular从零到一
相关文章
相关标签/搜索