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 的资源请求

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 无法得到请求结果

当然,如果我们使用 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] 的形式表示第一、第二和第三个参数。那么 ?#{} 是表示里面的内容是个 SpEL ( Spring 的表达式语言) 表达式。

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

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

在MongoDB的console查询

数据的分页

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

// 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

(0)

相关推荐

  • SpringBoot JPA 表关联查询实例

    今天给大家介绍一下如何利用JPA实现表关联查询. 今天给大家举一个一对多的关联查询,并且是使用JPA原生的findBy语句实现的. 例子中总共有两个实体类,一个是Floor(商品楼层类),另一个是FloorContent(商品楼层内容表).下面看两张表的源代码: Floor类: package cms.model; import cms.model.base.BaseDomain; import org.hibernate.annotations.GenericGenerator; import

  • SpringBoot多表联查(测试可用)

    今天在写的时候,遇到了多表查询的问题,网上有好多可以说是好烦,对于习惯了写sql的我来说,简直... 好啦,直接分享代码吧. public interface FieldValueDao extends JpaRepository<FieldValue,Integer> { @Query("select fv from FieldValue fv where field_id IN (select id from Field where table_id=:tableId) ORDE

  • 基于SpringMVC+Bootstrap+DataTables实现表格服务端分页、模糊查询

    前言 基于SpringMVC+Bootstrap+DataTables实现数据表格服务端分页.模糊查询(非DataTables Search),页面异步刷新. 说明:sp:message标签是使用了SpringMVC国际化 效果 DataTable表格 关键字查询 自定义关键字查询,非DataTable Search 代码 HTML代码 查询条件代码 <!-- 查询.添加.批量删除.导出.刷新 --> <div class="row-fluid"> <di

  • AJAX +SpringMVC 实现bootstrap模态框的分页查询功能

    一 .效果图 二.JS function getManagerList(dealerId,page2){ macAddress = document.getElementById("activeXDemo").getMac(); $.get("${ctxPath}/common/dealer/manager?"+Math.random(), { page2: page2, pageSize2: 9, dealerId: dealerId, macAddress:ma

  • Spring Boot中防止递归查询的两种方式

    本文主要给大家介绍了关于Spring Boot防止递归查询的相关内容,这只是一个小提醒,这里有两种方式,很简单,下面来看看详细的介绍: 1.在application.properties中配置 #懒加载配置 spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true 2.在entity中添加注解 在关联对象上添加@JsonBackReference 在类上添加@JsonIgnoreProperties("roles")

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

    跨域 前面我们初步做出了一个可以实现受保护的 REST API,但是我们没有涉及一个前端领域很重要的问题,那就是跨域请求( cross-origin HTTP request ).先来回顾一些背景知识: 跨域请求 定义:当我们从本身站点请求不同域名或端口的服务所提供的资源时,就会发起跨域请求. 例如最常见的我们很多的 css 样式文件是会链接到某个公共 CDN 服务器上,而不是在本身的服务器上,这其实就是典型的一个跨域请求.但浏览器由于安全原因限制了在脚本( script )中发起的跨域 HTT

  • Java Spring boot 2.0 跨域问题的解决

    跨域 一个资源会发起一个跨域HTTP请求(Cross-site HTTP request), 当它请求的一个资源是从一个与它本身提供的第一个资源的不同的域名时 . 比如说,域名A(http://domaina.example)的某 Web 应用程序中通过标签引入了域名B(http://domainb.foo)站点的某图片资源(http://domainb.foo/image.jpg),域名A的那 Web 应用就会导致浏览器发起一个跨站 HTTP 请求.在当今的 Web 开发中,使用跨站 HTTP

  • Spring Boot和Vue跨域请求问题原理解析

    这篇文章主要介绍了Spring Boot和Vue跨域请求问题原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 使用Spring Boot + Vue 做前后端分离项目搭建,实现登录时,出现跨域请求 Access to XMLHttpRequest at 'http://localhost/open/login' from origin 'http://localhost:8080' has been blocked by CORS pol

  • Spring Boot设置支持跨域请求过程详解

    现代浏览器出于安全的考虑, HTTP 请求时必须遵守同源策略,否则就是跨域的 HTTP 请求,默认情况下是被禁止的,IP(域名)不同.或者端口不同.协议不同(比如 HTTP.HTTPS)都会造成跨域问题. 一般前端的解决方案有: ① 使用 JSONP 来支持跨域的请求,JSONP 实现跨域请求的原理简单的说,就是动态创建<script>标签,然后利用<script>的 SRC 不受同源策略约束来跨域获取数据.缺点是需要后端配合输出特定的返回信息. ② 利用反应代理的机制来解决跨域的

  • Spring boot 总结之跨域处理cors的方法

    背景 现在做的很多项目都是前后端分离的,这就引出一个很常见的问题,我们的页面和接口是在不同域名下的,当我们通过ajax访问后端接口的时候就会出现跨域问题,这种问题我们怎么解决呢?一般来说就是cors和jsonp这两种方案.Spring简化了cors的配置,接下来我们来看一下它提供的cors. 跨域问题描述 Web开发经常会遇到跨域问题,解决方案有:jsonp,iframe,CORS等等. CORS 与 JSONP 相比: 1. JSONP 只能实现 GET 请求,而 CORS 支持所有类型的 H

  • SpringBoot+Spring Security无法实现跨域的解决方案

    SpringBoot+Spring Security无法实现跨域 未使用Security时跨域: import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springf

  • spring Cloud微服务跨域实现步骤

    这篇文章主要介绍了spring Cloud微服务跨域实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 第一步:在gateway网关的配置文件中加上下面这些: ly: cors: allowedOrigins: - http://manage.leyou.com - http://xxx.xxx.com # 允许哪些网址就继续加,不要写 *,否则cookie就无法使用了 allowedCredentials: true # 代表携带cook

  • spring cloud gateway请求跨域问题解决方案

    这篇文章主要介绍了spring cloud gateway请求跨域问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 代码如下 @Configuration public class CorsConfig implements GlobalFilter, Ordered { private static final String ALL = "*"; private static final String MAX_AGE =

  • 解决spring boot启动扫描不到自定义注解的问题

    对于自定义注解这里就不唠叨了,百度一大堆,这里有我一个自定义注解 @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD }) public @interface MsgEvent { RetailOrderEvent msgEvent(); } 注解实现类 @Component public class MsgEventProcessor implements BeanPostProcessor { /** * 事件消息

  • Spring Boot 集成Elasticsearch模块实现简单查询功能

    目录 背景 系统集成 引入jar包 application.yml文件中添加ES配置 创建文档实体 接口实现 具体实现 基础查询 新增文档 请求参数 Controller实现 返回结果 修改文档 通过id查询文档信息 Controller实现 删除文档 Controller实现 分页查询 Controller实现 返回结果 模糊查询 Controller实现 范围查询 Controller实现 总结 背景 项目中我们经常会用搜索功能,普通的搜索我们可以用一个SQL的like也能实现匹配,但是搜索

随机推荐