- 集成REST
org.springframework.boot
spring-boot-starter-web
- ResTemplate
Spring Boot提供了ResTemplate
来辅助发起一个REST请求,默认通过JDK自带的HttpURLConnection
来作为底层的HTTP消息发送的方式,使用JackSon
来序列化服务返回的JSON
数据。
-
ResTemplate
是核心类,提供了所有访问REST服务的接口。
HTTP Method | JavaAPI |
---|---|
DELETE | delete |
GET | getForObject,getForEntity |
HEAD | headForHeaders |
OPTIONS | optionsForAllow |
POST | postForObject,postForLocation |
PUT | put |
其他 | exchange(通用) |
- Spring Boot提供了
ResTemplateBulider
来创建一个ResTemplate
@Resource
private RestTemplateBuilder restTemplateBuilder;
public void foo(){
RestTemplate restTemplate= restTemplateBuilder.build();
}
@RestController
@RequestMapping(value ="/api")
@Slf4j
@PropertySource(value = "classpath:config/constant.properties")
public class DemoController {
@Value(value = "${api.user}")
private String URI;
@Resource
private RestTemplateBuilder restTemplateBuilder;
@GetMapping(value = "/user/{id}")
public User testUser(@PathVariable String id){
RestTemplate restTemplate =restTemplateBuilder.build();
String url=URI+"/user/queryByInfo?openId="+id;
User user=restTemplate.getForObject(url,User.class,id);
return user;
}
}
用postman测试一下:localhost:8987/api/user/111
返回结果如下:
{
"userId": "1633c318f09f4071a1609cb5b1a952c5",
"userOpenid": "111",
"userName": "不如吃茶去",
"userImage": "https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83eqTrMHUs50DDYOiaB8j6Fibvge3DfgO8nskx2s2vPsut3AvyvYTibRYXoeG19kYlsozm9VXd4hGQP4icg/132",
"userReport": 3,
"userTel": "123",
"userSname": "3",
"userSno": "3",
"userStatus": 1
}
然后来说一下getForObject
方法,第一个参数是URI
模板,第二个参数是期待返回的对象,后面是URI
模板对应的参数列表。参数列表既可以是数组,也可以是Map
,以上代码也可以写成:
Map map=new HashMap<>();
map.put("id",id);
User user=restTemplate.getForObject(url,User.class,map);
还可以获取返回HTTP相关的头信息,可以调用restTemplate.getEntity
,此方法返回RespomseEntity
,包含了头信息:
ResponseEntity responseEntity=restTemplate.getForEntity(url,User.class,id);
User user=responseEntity.getBody();
HttpHeaders httpHeaders=responseEntity.getHeaders();
log.info("httpheaders:{}",httpHeaders);
// 打印输出的结果
httpheaders:[Content-Type:"application/json;charset=UTF-8", Transfer-Encoding:"chunked", Date:"Mon, 18 Mar 2019 13:20:06 GMT"]
- 添加信息时可以使用
postForObject
方法,此方法接受三个参数,第一个是URI
,第二个是Post
参数,可以是HttpEntity
,或者是某个POJO
对象,POJO
对象在这种情况下会自动转成HttpEntity
,第三个参数是返回值类型:
@RestController
@RequestMapping(value ="/api")
@Slf4j
@PropertySource(value = "classpath:config/constant.properties")
public class DemoController {
@Value(value = "${api.user}")
private String URI;
@Resource
private RestTemplateBuilder restTemplateBuilder;
@GetMapping(value = "/user")
public String testUser(){
RestTemplate restTemplate =restTemplateBuilder.build();
String url=URI+"/user";
User user=User.builder().userId("xxx")
.userImage("xxx")
.userName("xxx")
.userOpenid("xxx")
.userTel("xxx").build();
HttpEntity body=new HttpEntity<>(user);
ResponseEntity responseEntity =restTemplate.postForEntity(url,body,String.class);
String ret=responseEntity.getBody();
//{code:200}
return ret;
}
}
注: 最好使用HttpEntuty
,可以提供额外的HTTP头信息,比如在用于权限验证时添加jwt。(对于权限认证放在前端还是放在后端,以及动态权限的实现,目前为止还是不太清楚。)
以下来自一位大佬的总结:
先说一说我权限控制的主体思路,前端会有一份路由表,它表示了每一个路由可访问的权限。当用户登录之后,通过 token 获取用户的 role ,动态根据用户的 role 算出其对应有权限的路由,再通过router.addRoutes动态挂载路由。但这些控制都只是页面级的,说白了前端再怎么做权限控制都不是绝对安全的,后端的权限验证是逃不掉的。
我司现在就是前端来控制页面级的权限,不同权限的用户显示不同的侧边栏和限制其所能进入的页面(也做了少许按钮级别的权限控制),后端则会验证每一个涉及请求的操作,验证其是否有该操作的权限,每一个后台的请求不管是 get 还是 post 都会让前端在请求 header里面携带用户的 token,后端会根据该 token 来验证用户是否有权限执行该操作。若没有权限则抛出一个对应的状态码,前端检测到该状态码,做出相对应的操作。
权限 前端or后端 来控制?
有很多人表示他们公司的路由表是于后端根据用户的权限动态生成的,我司不采取这种方式的原因如下
1.项目不断的迭代你会异常痛苦,前端新开发一个页面还要让后端配一下路由和权限,让我们想了曾经前后端不分离,被后端支配的那段恐怖时间了。
2.其次,就拿我司的业务来说,虽然后端的确也是有权限验证的,但它的验证其实是针对业务来划分的,比如超级编辑可以发布文章,而实习编辑只能编辑文章不能发布,但对于前端来说不管是超级编辑还是实习编辑都是有权限进入文章编辑页面的。所以前端和后端权限的划分是不太一致。
3.还有一点是就vue2.2.0之前异步挂载路由是很麻烦的一件事!不过好在官方也出了新的api,虽然本意是来解决ssr的痛点的。
addRoutes
在之前通过后端动态返回前端路由一直很难做的,因为vue-router必须是要vue在实例化之前就挂载上去的,不太方便动态改变。不过好在vue2.2.0以后新增了router.addRoutes
- 如果期望返回的类信号是一个列表,不能简单的使用
xxxForObject
,因为存在泛型的类型擦除,Restemplate
在反序列化的时候并不知道实际返回反序列化的类型,因此可以使用ParameterzedTypeReference
来包含泛型类型:
@GetMapping(value = "/user")
public List testUser(){
RestTemplate restTemplate =restTemplateBuilder.build();
String url=URI+"/lost";
ParameterizedTypeReference> typeReference=new ParameterizedTypeReference>() {};
// HttpHeaders headers = new HttpHeaders();
// 请勿轻易改变此提交方式,大部分的情况下,提交方式都是表单提交
// headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// 封装参数,千万不要替换为Map与HashMap,否则参数无法传递
// MultiValueMap params= new LinkedMultiValueMap<>();
// params.add("pageIndex", 1);
// params.add("pageSize", 5);
// HttpEntity> body = new HttpEntity<>(paranms,headers);
HttpEntity body=null;
//exchange是一个基础的REST调用接口,需要指明HTTP Method,调用方法和其他方法类似
ResponseEntity> rs=restTemplate.exchange(url, HttpMethod.GET,body,typeReference,LostVo.class);
List users=rs.getBody();
return users;
}
注:
-
typeRef
定义是用{}结束的,这里创建一个ParameterizedTypeReference
子类,依据在类定义中的泛型信息保留的原则,typeRef
保留了期望返回的泛型List。如果有参数,需要用HttpEntity
进行封装。 - 除了使用
ParameterizedTypeReference
来保留泛型信息,也可以通过getForObject
方法先映射成String
,然后通过ObjectMapper
转为指定类型