乐趣区

Spring Boot 发起 HTTP 请求

起步
新年目标 Spring Cloud 开始实施,打开慕课网。

刚学了一章,大体就是调用中国天气网的 api,使用 Spring Boot 构建自己的天气预报系统,然后使用 Spring Cloud,一步一步使用微服务的思想来演进架构。
小目标
昨天去百度抢了新年红包,感叹百度的高并发做的也是如此优秀。
阿里的双十一,百度的新年。(发现了二者的共同点,可能也是解决并发的一种思路,并发的时候只允许增加数据。)
感叹归感叹,期望着学习完 Spring Cloud 也能设计出优秀的架构,解决并发的一些问题。
遇到的问题
学习时也跟着课程进行编码,讲师讲的非常好,但是本课程的重点是后面的微服务架构,所以前面的功能有一些瑕疵,特此提出自己的实现,供大家学习交流。
功能描述
最初的功能很简单,因为后台是没有任何数据的,所以前台有请求,就直接去天气网要数据,然后再返回去。

数据序列化问题
这是天气网 api 返回来的数据格式,乍一看没啥毛病。
{
“data”: {
“yesterday”: {
“date”: “4 日星期一 ”,
“high”: “ 高温 26℃”,
“fx”: “ 无持续风向 ”,
“low”: “ 低温 18℃”,
“fl”: “<![CDATA[<3 级]]>”,
“type”: “ 多云 ”
},
“city”: “ 深圳 ”,
“forecast”: [
{
“date”: “5 日星期二 ”,
“high”: “ 高温 25℃”,
“fengli”: “<![CDATA[<3 级]]>”,
“low”: “ 低温 18℃”,
“fengxiang”: “ 无持续风向 ”,
“type”: “ 多云 ”
},
{
“date”: “6 日星期三 ”,
“high”: “ 高温 26℃”,
“fengli”: “<![CDATA[<3 级]]>”,
“low”: “ 低温 17℃”,
“fengxiang”: “ 无持续风向 ”,
“type”: “ 多云 ”
},
{
“date”: “7 日星期四 ”,
“high”: “ 高温 27℃”,
“fengli”: “<![CDATA[<3 级]]>”,
“low”: “ 低温 18℃”,
“fengxiang”: “ 无持续风向 ”,
“type”: “ 多云 ”
},
{
“date”: “8 日星期五 ”,
“high”: “ 高温 26℃”,
“fengli”: “<![CDATA[<3 级]]>”,
“low”: “ 低温 17℃”,
“fengxiang”: “ 无持续风向 ”,
“type”: “ 多云 ”
},
{
“date”: “9 日星期六 ”,
“high”: “ 高温 24℃”,
“fengli”: “<![CDATA[<3 级]]>”,
“low”: “ 低温 14℃”,
“fengxiang”: “ 无持续风向 ”,
“type”: “ 小雨 ”
}
],
“ganmao”: “ 相对今天出现了较大幅度降温,较易发生感冒,体质较弱的朋友请注意适当防护。”,
“wendu”: “23”
},
“status”: 1000,
“desc”: “OK”
}
缺点 1:有拼音;ganmao、wendu。
缺点 2:名称不一致;理论上来说 yesterday 与 forecast 应该是同一个实体,都表示一天的天气情况,只是名称不同。但是在 yesterday 中,风向和风力是 fx 和 fl,在 forecast 中,名称却是 fengli、fengxiang。
解决此问题,想到的思路就是使用 jackson 进行序列化与反序列化时进行配置的一些注解。
最初使用此种方法实现:
@JsonProperty(“wendu”)
private Float temperature;
一个对象中的名字,一个 json 数据中的名字。
可以实现,但是不好。
举个例子,天气 api 返回给我 wendu,添加了 @JsonProperty,然后 wendu 就绑定到了 temperature 上,但是如果我前台再返回该对象,序列化后生成的名称还是 wendu。不好!
目标是实现,反序列化时:从 wendu 能绑定到我的 temperature,序列化时直接使用我的字段名。
get、set 尝试
猜测是不是和 get、set 方法有关。
就把 @JsonProperty(“wendu”) 添加到 set 方法上,发现并没有用。
JsonAlias
后来经过查询,原来是注解用错了,此种情况应使用别名。
关于 JsonProperty 和 JsonAlias 的详细讲解,请参考 Jackson @JsonProperty and @JsonAlias Example。
@JsonAlias(“wendu”)
private Float temperature;
同时,可以应用多个别名:
@JsonAlias({“fengli”, “fl”})
private String windForce;
发起请求
发起请求的示例代码,供以后参考。
@Autowired
private RestTemplate restTemplate;

@Override
public Weather getWeatherByCityName(String cityName) {
return this.getWeatherByUrl(BASE_URL + “?” + CITY_NAME + “=” + cityName)
.getData();
}

private Response getWeatherByUrl(String url) {
// 发起 Get 请求
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
// 如果状态码非 200, 抛异常
if (response.getStatusCodeValue() != 200) {
throw new YunzhiNetworkException(“ 数据请求失败 ”);
}
// 实例化对象映射对象
ObjectMapper mapper = new ObjectMapper();
// 初始化响应数据
Response data;
// 从字符串转换为 Response 对象
try {
data = mapper.readValue(response.getBody(), Response.class);
} catch (IOException e) {
throw new YunzhiIOException(“json 数据转换失败 ”);
}
// 返回
return data;
}
RestTemplate 配置
这里与正常的 RestTemplate 构建有些不同,通常的 RestTemplate 是使用 Spring 工具类构造的,此处使用 Apache 的 Http 组件构造,以支持更多的数据格式。
implementation ‘org.apache.httpcomponents:httpclient’
同时去除了默认的对 String 的 Http 消息转换器,默认的转换器使用的不是 UTF- 8 编码。
讲师原文章:Spring RestTemplate 调用天气预报接口乱码的解决
@Configuration
public class BeanConfiguration {

@Bean
public RestTemplate restTemplate() {
// 使用 Apache HttpClient 构建 RestTemplate, 支持的比 Spring 自带的更多
RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
// 去除默认的 String 转换器
restTemplate.getMessageConverters().removeIf(converter -> converter instanceof StringHttpMessageConverter);
// 添加自定义的 String 转换器, 支持 UTF-8
restTemplate.getMessageConverters()
.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
return restTemplate;
}
}
更完善的单元测试
同时,在编写单元测试的时候,看了一篇关于 AssertJ 的文章。Testing with AssertJ assertions – Tutorial
之前学 Junit5 的时候,觉得这个东西挺好使的啊?为什么被开源社区抛弃而使用 AssertJ 呢?
原来之前用的断言都太简单,其实 AssertJ 远比我们使用的更强大。
@Test
public void getWeatherByCityName() throws Exception {
final String cityName = “ 深圳 ”;
MvcResult mvcResult = this.mockMvc
.perform(MockMvcRequestBuilders.get(BASE_URL + “/cityName/” + cityName))
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String json = mvcResult.getResponse().getContentAsString();
Assertions.assertThat(json)
.contains(“cityName”, “cold”, “temperature”, “windDirection”, “windForce”)
.doesNotContain(“ganmao”, “wendu”, “fx”, “fl”, “fengxiang”, “fengli”);
}
总结
多看英文文章,Tutorial 写得都特别好。

退出移动版