天气信息提供商有很多,有心知天气,聚合数据,还有一些是跟阿里云合作的,我这边根据项目需求和收费情况选择了心知天气,因为心知天气免费版每小时提供的接口调用次数限制是400次/小时,所以这里采用redis进行数据缓存来解决该问题
1.到官网进行注册
注册后登入可以获得一个key值
2.获得key值后就可以进行https接口调用了
2.1 创建一个心知天气信息工具类
该类用于获取天气信息和生活指数两个接口,这两个接口返回ResponseEntity<String>对象,这是spring提供的标准对象,将在业务层对该对象进行解析
package com.lantaiyuan.ebus.common.weather.seniverse;
import org.springframework.http.ResponseEntity;
/**
* 描述:心知天气
* 作者:温海金
* 最后更改时间:下午2:16:19
*/
public class SeniverseWeatherHelper {
/**
* 注册KEY值
*/
private static String KEY = "2poa1sq8xbc0dnxo";
/**
* 天气接口前缀
*/
public static String WEATHER_URL_PRE = "https://api.seniverse.com/v3/weather/now.json?language=zh-Hans&unit=c";
/**
* 生活指数接口前缀
*/
public static String LIFE_URL_PRE = "https://api.seniverse.com/v3/life/suggestion.json?language=zh-Hans";
/**
* 功能描述:返回接口调用结果
* 作者:温海金
* 最后更改时间 : 2017年4月19日 下午2:40:10
*/
public static ResponseEntity<String> getSeniverseInterfaceResult(String URL, String cityName) {
return getResultOfRequest(URL+"&location="+cityName.trim()+"&key="+KEY);
}
/**
* 功能描述:获取URL执行结果
* 作者:温海金
* 最后更改时间 : 2017年4月19日 下午2:40:10
*/
private static ResponseEntity<String> getResultOfRequest(String url) {
return RestTemplateSigleton.getInstance().getForEntity(url, String.class);
}
}
2.2 定义一个单例的RestTemplate对象
在上面的工具类中,https接口的访问是通过spring的RestTemplate来操作的:
/**
* 功能描述:获取URL执行结果
* 作者:温海金
* 最后更改时间 : 2017年4月19日 下午2:40:10
*/
private static ResponseEntity<String> getResultOfRequest(String url) {
return RestTemplateSigleton.getInstance().getForEntity(url, String.class);
}
这是spring提供的rest接口访问模板对象
package com.lantaiyuan.ebus.common.weather.seniverse;
import org.springframework.web.client.RestTemplate;
/**
* 描述:http请求模板对象实例类
* 作者:温海金
* 最后更改时间:下午2:19:49
*/
public class RestTemplateSigleton {
/**
* spring提供的rest接口访问模板对象
*/
private static RestTemplate restTemplate = null;
public static RestTemplate getInstance() {
if(restTemplate == null) {
synchronized (RestTemplateSigleton.class) {
if(restTemplate == null) {
restTemplate = new RestTemplate();
}
}
}
return restTemplate;
}
}
3 通过alibaba提供的JSON工具类解析ResponseEntity<String>对象
package com.lantaiyuan.ebus.custom.service.impl;
import java.util.Collections;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.lantaiyuan.ebus.common.weather.seniverse.SeniverseWeatherHelper;
import com.lantaiyuan.ebus.custom.model.NoticeHistory;
import com.lantaiyuan.ebus.custom.model.SeniverseLife;
import com.lantaiyuan.ebus.custom.model.SeniverseWeather;
import com.lantaiyuan.ebus.custom.model.SeniverseWeatherAndLife;
import com.lantaiyuan.ebus.custom.model.WeatherInfoAndNoticeHistory;
import com.lantaiyuan.ebus.custom.service.NoticeHistoryServiceI;
import com.lantaiyuan.ebus.custom.service.WeatherServiceI;
@CacheConfig(cacheNames="weatherInfo")
@Service("weatherService")
public class WeatherServiceImpl implements WeatherServiceI {
@Resource
private NoticeHistoryServiceI noticeHistoryService;
/**
* 功能描述:得到聚合天气及生活指数信息
* 作者:温海金
* 最后更改时间 : 2017年4月20日 下午4:47:18
*/
@Cacheable
private SeniverseWeatherAndLife getSeniverseWeather(String cityName) {
//1.得到天气信息
ResponseEntity<String> weatherInfo = SeniverseWeatherHelper.getSeniverseInterfaceResult(SeniverseWeatherHelper.WEATHER_URL_PRE, cityName);
String weatherBody = weatherInfo.getBody();
JSONObject weatherJSONObj = JSONObject.parseObject(weatherBody);
//得到天气对象数组
JSONArray weatherArray = weatherJSONObj.getJSONArray("results");
//得到数组内的对象
JSONObject weatherObject = weatherArray.getJSONObject(0);
//得到当前天气信息对象
JSONObject now = weatherObject.getJSONObject("now");
SeniverseWeather seniverseWeather = JSON.parseObject(now.toJSONString(), SeniverseWeather.class);
//2.得到生活指数
ResponseEntity<String> lifeInfo = SeniverseWeatherHelper.getSeniverseInterfaceResult(SeniverseWeatherHelper.LIFE_URL_PRE, cityName);
JSONObject lifeJsonObj = JSONObject.parseObject(lifeInfo.getBody());
JSONObject lifeObject = lifeJsonObj.getJSONArray("results").getJSONObject(0);
JSONObject suggestion = lifeObject.getJSONObject("suggestion");
String flu = suggestion.getJSONObject("flu").get("brief").toString();//感冒指数
String sport = suggestion.getJSONObject("sport").get("brief").toString();//运动指数
String uv = suggestion.getJSONObject("uv").get("brief").toString();//紫外线
return new SeniverseWeatherAndLife(seniverseWeather, new SeniverseLife(sport, flu, uv));
}
/**
* 功能描述:根据用户id得到通知记录信息
* 作者:温海金
* 最后更改时间 : 2017年4月19日 下午5:41:10
*/
private List<NoticeHistory> getHistorysByUserId(String userId) {
return noticeHistoryService.getNoticeHistorysByUserId(userId);
}
/**
* 功能描述:得到心知天气信息(包括天气及生活指数信息)和用户的历史通知记录
* 作者:温海金
* 最后更改时间 : 2017年4月20日 下午3:15:08
*/
@Override
public WeatherInfoAndNoticeHistory getSeniverseWeatherAndNoticeHistory(String cityName, String userId) {
//1.得到天气信息
SeniverseWeatherAndLife seniverseWeather = this.getSeniverseWeather(cityName);
//2.得到历史通知记录信息
List<NoticeHistory> noticeHistorys;
if(StringUtils.isEmpty(userId)) {//未登入用户返回空集合
noticeHistorys = Collections.emptyList();
} else {
noticeHistorys = getHistorysByUserId(userId);
}
return new WeatherInfoAndNoticeHistory(seniverseWeather, noticeHistorys);
}
}
以上方法涉及到json对象的解析,以下通过断点调试查看alibaba的json解析器是在什么情况下使用的
3.1 通过spring的RestTemplate获取的心知天气信息weatherInfo格式如下:
<200 OK,{"results":[{"location":{"id":"WS10730EM8EV","name":"深圳","country":"CN","path":"深圳,深圳,广东,中国","timezone":"Asia/Shanghai","timezone_offset":"+08:00"},"now":{"text":"多云","code":"4","temperature":"27"},"last_update":"2017-04-20T18:00:00+08:00"}]},{Date=[Thu, 20 Apr 2017 10:21:35 GMT], Content-Type=[application/json; charset=utf-8], Content-Length=[266], Connection=[keep-alive], Cache-Control=[no-cache], Pragma=[no-cache], Expires=[-1]}>
3.2 通过weatherInfo.getBody()得到具体对象信息
{"results":[{"location":{"id":"WS10730EM8EV","name":"深圳","country":"CN","path":"深圳,深圳,广东,中国","timezone":"Asia/Shanghai","timezone_offset":"+08:00"},"now":{"text":"多云","code":"4","temperature":"27"},"last_update":"2017-04-20T18:00:00+08:00"}]}
这是一个字符串,通过JSONObject.parseObject(String str)转化为JSONObject对象weatherJSONObj
将其格式化如下:
3.3 获取JSONObject对象中的数组
weatherJSONObj对象中的results是一个数组
通过JSONArray weatherArray = weatherJSONObj.getJSONArray("results");来得到数组信息:
weatherArray
[{"last_update":"2017-04-20T18:15:00+08:00","location":{"country":"CN","id":"WS10730EM8EV","name":"深圳","path":"深圳,深圳,广东,中国","timezone":"Asia/Shanghai","timezone_offset":"+08:00"},"now":{"code":"4","temperature":"26","text":"多云"}}]
3.4 获取数组中的对象
数组weatherArray中只有一个对象,通过JSONArray的getJSONObject(int index)获得该对象
JSONObject weatherObject = weatherArray.getJSONObject(0);
weatherObject 格式如下:
{"last_update":"2017-04-20T18:15:00+08:00","location":{"country":"CN","id":"WS10730EM8EV","name":"深圳","path":"深圳,深圳,广东,中国","timezone":"Asia/Shanghai","timezone_offset":"+08:00"},"now":{"code":"4","temperature":"26","text":"多云"}}
再通过JSONObject now = weatherObject.getJSONObject("now");得到对象中今天的天气信息:
now的结构:{"code":"4","temperature":"26","text":"多云"}
3.5 获取JSONObject中的具体属性值
String text = now.get("text").toString();
该方法得到的值为:“多云”
4 JSONObject对象与javaBean的转化
4.1 调用以下方法将当前天气信息转化为javaBean
SeniverseWeather seniverseWeather = JSON.parseObject(now.toJSONString(), SeniverseWeather.class);
javaBean的定义如下:
package com.lantaiyuan.ebus.custom.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* 描述:心知天气-天气信息对象
* 作者:温海金
* 最后更改时间:下午6:53:11
*/
public class SeniverseWeather {
/**
* 天气情况
*/
private String text;
/**
* 返回编码(不展示给前端)
*/
@JsonIgnore
private String code;
/**
* 温度
*/
private String temperature;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getTemperature() {
return temperature;
}
public void setTemperature(String temperature) {
this.temperature = temperature;
}
}
4.2 @JsonIgnore注解的使用
在以上javaBean中,使用@JsonIgnore注解的作用是隐藏code字段,不将“返回编码信息”返回给前端
5 给天气接口加缓存
心知天气每小时调用400次,所以将该接口的调用结果缓存到redis中
1.在spring配置文件中添加缓存配置信息
<bean id="redisCacheConfig" class="com.lantaiyuan.ebus.common.redis.RedisCacheConfig" />
该缓存配置类的具体实现如下:
package com.lantaiyuan.ebus.common.redis;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory cf) {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
redisTemplate.setConnectionFactory(cf);
return redisTemplate;
}
@Bean
public CacheManager cacheManager(RedisTemplate<?, ?> redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setDefaultExpiration(3000);
Map<String, Long> expires = new HashMap<>();
expires.put("nearStation", 15L);//缓存15秒
expires.put("routeDetail", 5L);
expires.put("cityCodes", 600L);//缓存10分钟
expires.put("weatherInfo", 600L);//天气信息缓存10分钟
cacheManager.setExpires(expires);
return cacheManager;
}
/**
* 自定义key. 此方法将会根据类名+方法名+所有参数的值生成唯一的一个key,即使@Cacheable中的value属性一样,key也会不一样。
*/
@Override
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object o, Method method, Object... objects) {
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getName());
sb.append(method.getName());
for (Object obj : objects) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
}
该类中定义了一个expires.put("weatherInfo", 600L)用来定义redis中缓存天气信息的命名空间,下面会用到
2. 在业务类上加缓存配置信息@CacheConfig(cacheNames="weatherInfo")
表示将该类中的缓存存储到redis中的weatherInfo域中,可理解为命名空间
@CacheConfig(cacheNames="weatherInfo")
@Service("weatherService")
public class WeatherServiceImpl implements WeatherServiceI {
…………
}
3.为该类需要添加缓存的方法加注解@Cacheable
/**
* 功能描述:得到聚合天气及生活指数信息
* 作者:温海金
* 最后更改时间 : 2017年4月20日 下午4:47:18
*/
@Cacheable
private SeniverseWeatherAndLife getSeniverseWeather(String cityName) {
//1.得到天气信息
ResponseEntity<String> weatherInfo = SeniverseWeatherHelper.getSeniverseInterfaceResult(SeniverseWeatherHelper.WEATHER_URL_PRE, cityName);
String weatherBody = weatherInfo.getBody();
JSONObject weatherJSONObj = JSONObject.parseObject(weatherBody);
//得到天气对象数组
JSONArray weatherArray = weatherJSONObj.getJSONArray("results");
//得到数组内的对象
JSONObject weatherObject = weatherArray.getJSONObject(0);
//得到当前天气信息对象
JSONObject now = weatherObject.getJSONObject("now");
SeniverseWeather seniverseWeather = JSON.parseObject(now.toJSONString(), SeniverseWeather.class);
//2.得到生活指数
ResponseEntity<String> lifeInfo = SeniverseWeatherHelper.getSeniverseInterfaceResult(SeniverseWeatherHelper.LIFE_URL_PRE, cityName);
JSONObject lifeJsonObj = JSONObject.parseObject(lifeInfo.getBody());
JSONObject lifeObject = lifeJsonObj.getJSONArray("results").getJSONObject(0);
JSONObject suggestion = lifeObject.getJSONObject("suggestion");
String flu = suggestion.getJSONObject("flu").get("brief").toString();//感冒指数
String sport = suggestion.getJSONObject("sport").get("brief").toString();//运动指数
String uv = suggestion.getJSONObject("uv").get("brief").toString();//紫外线
return new SeniverseWeatherAndLife(seniverseWeather, new SeniverseLife(sport, flu, uv));
}
这样就实现了天气信息的缓存