这个春节, 应该会让我们永世难忘! 每天早上醒来第一件事就是看新型肺炎相关的消息, 白天就是宅在家做饭/吃饭/看电视. 两天前艰难地回到上海了, 春节假期延长了, 却无心干正事, 每天就在那刷疫情相关的消息. 在刷掘金沸点时, 看到有的网友用天行数据开放的 API 做了个疫情小工具. 我想闲着也无聊, 我也来写一个吧!
概述
体验网址: kaiyuanshuwu.cn/ncov.html, 截图如下:
主要功能:
- 全国, 以及分省份/城市的确诊病例/疑似病例/死亡人数/治愈人数
- 疫情相关消息/谣言滚动播报
- 新型肺炎确诊患者同行程查询
主要技术栈:
- 数据
- 开放 API: 天行数据
- retrofit2, 将 API 转化为 "微服务"
- redis, 缓存 10min, 减少对 API 的请求 (天行也是大约 10min 更新一次数据)
- 后端
- Java
- Spring Boot
- 前端
- Vue
- Element UI
- EChart
数据对接
登录天行数据, 申请相关 API 接口. 为了更优化地对接 API 接口, 我们用 retrofit2, 将其转换为 "微服务" 形式. 引入如下两个依赖
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>retrofit</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>converter-jackson</artifactId>
<version>2.0.2</version>
</dependency>
复制代码
引入 converter-jackson
的目的是 json 转 java 对象.
定义 TianService
接口, 将 API 映射为对应的接口方法, 然后增加配置文件 TianConfig
, 生成 tianService
Bean.
@Configuration
public class TianConfig {
@Autowired
private GlobalSetting globalSetting;
@Autowired
private Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder;
@Bean
public TianService tianService() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(globalSetting.getTianHost())
.client(new OkHttpClient.Builder()
.connectTimeout(3000, TimeUnit.MILLISECONDS)
.writeTimeout(1000, TimeUnit.MILLISECONDS)
.readTimeout(1000, TimeUnit.MILLISECONDS).build())
.addConverterFactory(JacksonConverterFactory.create(
jackson2ObjectMapperBuilder.build()))
.build();
return retrofit.create(TianService.class);
}
}
复制代码
字段重命名
在对接 API 时, 经常会需要对 JSON 串的字段进行重命名, 比如下划线改驼峰.
建议使用 @JsonProperty
注解.
不过, 如果这个 Java 对象经过 Spring MVC 再次转为 JSON 串时, 又恢复原样了, 因为 Spring MVC 也是用的 jackson.
优雅的解决方案是用 fastjson
替换 Spring MVC 的默认配置.
@Configuration
public class FastJsonHttpMessageConverterConfig {
@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
SerializeConfig serializeConfig = SerializeConfig.globalInstance;
// 初始化一个转换器配置
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializeConfig(serializeConfig);
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat,
SerializerFeature.WriteNonStringKeyAsString,
SerializerFeature.DisableCircularReferenceDetect);
// 初始化转换器
FastJsonHttpMessageConverter fastConvert = new FastJsonHttpMessageConverter();
fastConvert.setFastJsonConfig(fastJsonConfig);
fastConvert.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
// 将配置设置给转换器并添加到HttpMessageConverter转换器列表中
return new HttpMessageConverters(fastConvert);
}
}
复制代码
数据缓存
接下来, 在我们 Controller 层调用 TianService
服务即可, 为了减少对 API 的请求次数, 增加一个 Redis 缓存, 设置 Key 的缓存时间为 10min.
由于我的云服务器之前没有安装 Redis, 这里先装一下
yum install -y redis
service redis start
chkconfig redis on
复制代码
接下来就可以正常使用了. Java 访问 Redis, 我们用 spring-boot-starter-data-redis
, 先引入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
复制代码
版本号 spring-boot 管理好了. 注意这里需要引入 jedis, 或 common-pool2, 用于生成连接池相关 Bean.
由于 spring-data-redis 自带的 redisTemplate Bean 是 RedisTemplate<Object, Object>
, 使用不方便, 因此我们重新定义如下.
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
}
复制代码
现在就可以很方便地缓存/读取数据啦!
redisTemplate.opsForValue().set(KEY_NCOV, response, 10, TimeUnit.MINUTES)
redisTemplate.opsForValue().get(KEY_NCOV)
复制代码
视图层
采用前后端完全分离的方式, 先增加一个 /ncov.html
页面, 它本质是一个 FreeMarker 页面, 引入了 Vue 打包后的 js 和 css.
其他数据采用 api 的方式给到前端. 前端用 axios 将数据引入 Vuex Store 中.
新增 src/ncov.js
文件, 并添加到 webpack 构建配置的入口列表中. 入口文件的主要内容是
new Vue({
el: '#app',
store,
render: h => h(NCov)
});
复制代码
其中 NCov
就是主要的视图页面.
疫情地图
丁香医生等平台提供的疫情地图, 用的是分段函数划分的严重等级, 这样模糊了一些区别.
这里用 EChart 的地图工具
, 然后用“确诊病例数除以100”给地图上色.
我在 Vue 中使用 EChart, 一般用自己简单封装的 EChart 组件. 注意: 为了使用地图工具, 还需要引入对应的 js.
import EChart from 'hui-vue/src/components/EChart';
import 'echarts/map/js/china';
复制代码
进一步思考
宅在家的这段时间, 阅读了很多内容, 其中, 感触最深的是一位CEO给员工的防疫指南:在不确定的世界强悍地活带来的思考. 自我隔离这段时间是很好的自学, 提升自我, 思考未来的时期.
我们正在经历一次可能漫长而危险的不确定性时期, 人民和国家损失惨重, 以人为本, 不计成本地建设 "小汤山" 医院, 壮士断腕, 做出封城封村, 延迟开工开学的决定. 每个人都关注疫情发展, 自我隔离, 坚信疫情拐点很快就会到来!!!