上一章里咱们已经能够手动设置天气状况,但在通常状况下,天气状况都是客观的,因此他不该该由人手动设置。因此读取天气接口自动获取就是一个必须的功能点了。前端
天气预报的接口有不少,最先的weather.cn有时好时坏,因此最终选择了心知天气的接口。vue
这个接口的免费版能够支持国内市级的几乎全部城市,这也是我在上一章把选择地区的精确度定为市级的缘由之一。而且能够根据名称和坐标等功能获取实时天气,当前阶段,免费版也能够支持现有的功能.java
心知天气的用法很简单,首先注册一个帐号,而后就回有一个key,接下来将key嵌入到url中就能够经过webapi的方式get回一个json的字符串,解析便可。git
为了将来程序的扩展性和保密性,天气api的key不能够写在代码内,能够选择保存在配置文件中或者创立一个字典表。通过多方便考虑,我选择使用字典表的方式来进行保存,首先在数据库中建立字典表,而后在程序中,他所对应的数据模型以下:github
@Entity(name = "dictionaryitems") public class DictionaryItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private Integer sort; //排序 private String dicname; //字典key private String dicvalue; //字典值 private String typevalue; //字典值类型(多项 分组用) //getter setter }
有了以前的框架,接下来的代码就比较容易了,仍是同样的,持久层jpa接口:web
public interface DictionaryItemRepository extends JpaRepository<DictionaryItem,Integer> { List<DictionaryItem> findByDicname(String dicName); //根据字典key获取值 List<DictionaryItem> findByTypevalue(String typeValue); //根据类型获取值 List<DictionaryItem> findByTypevalueOrderBySort(String typeValue);//根据类型获取值并排序 }
其实在当前,咱们须要使用的只有第一个。面试
须要注意,在当前不考虑作系统后台的状况下,此字典表均需手动录入,也就是或只有jpa层,不须要服务层vue-cli
如今假设你已经注册完成,而且进入面试使用api的界面,能够看到若干接口,由于属于欠高端用户,因此咱们只看接口名后边没有付费接口字样的接口。通过查询,很容易就找到咱们须要的:
逐日天气预报和昨日天气,能够看到接口路径为/weather/daily.json数据库
接口文档及参数:apache
key 你的API密钥 location 所查询的位置 参数值范围: 城市ID 例如:location=WX4FBXXFKE4F 城市中文名 例如:location=北京 省市名称组合 例如:location=辽宁朝阳、location=北京朝阳 城市拼音/英文名 例如:location=beijing(如拼音相同城市,可在以前加省份和空格,例:shanxi yulin) 经纬度 例如:location=39.93:116.40(格式是 纬度:经度,英文冒号分隔) IP地址 例如:location=220.181.111.86(某些IP地址可能没法定位到城市) “ip”两个字母 自动识别请求IP地址,例如:location=ip language 语言 (可选) unit 单位 (可选) 参数值范围: c 当参数为c时,温度c、风速km/h、能见度km、气压mb f 当参数为f时,温度f、风速mph、能见度mile、气压inch 默认值:c start 起始时间 (可选) 参数值范围: 日期 例如:start=2015/10/1 整数 例如:start=-2 表明前天、start=-1 表明昨天、start=0 表明今天、start=1 表明明天 默认值:0 days 天数 (可选) 返回从start算起days天的结果。默认为你的权限容许的最多天数。
通过筛选,咱们能够看到:
key:本身当前的key location:前端定位或选择的省市级组合单位 language:zh-Hans unit:c start:0(今天) days:1(不须要预报功能,只是实时查询今每天气)
ok,假设选择了北京,最终的参数查询url为:
https://api.seniverse.com/v3/weather/daily.json?key=mykey&location=北京北京&language=zh-Hans&unit=c&start=0&days=1
返回的查询结果为(已手动格式化):
{ "results":[ { "location":{ "id":"mykey", "name":"北京", "country":"CN", "path":"北京,北京,中国", "timezone":"Asia/Shanghai", "timezone_offset":"+08:00" }, "daily":[ { "date":"2018-02-11", "text_day":"晴", "code_day":"0", "text_night":"晴", "code_night":"1", "high":"0", "low":"-8", "precip":"", "wind_direction":"西北", "wind_direction_degree":"315", "wind_speed":"20", "wind_scale":"4" } ], "last_update":"2018-02-11T18:00:00+08:00" } ] }
下面看看,在这些属性中咱们须要的和不须要的,貌似除了时区和最后更新时间外,均须要能够保存,因此最终数据模型为:
@Entity(name = "weather") public class Weather { private Integer id; private String name; private String path; private String weatherdate; private String text_day; private Integer code_day; private Integer temp_high; private Integer temp_low; private String precip; private String wind_direction; private String wind_direction_degree; private String wind_speed; private String wind_scale; private Integer isweb; setter... getter... }
其中isweb的属性用来确认是网络获取仍是本地设置。
因为金钱的缘由,现有帐户每小时只能访问400次,因此须要必要的缓存机制缓存到本地,这样就不能由客户端直接访问心知天气的api,只能由服务器端缓存后在发送至客户端。这样,就须要java端进行必须的服务器访问操做。
按照RESTful的思想,访问的都是资源,也就是能够把它理解为一个网络数据库,因此一样,建立一个包用来存放web持久层,固然这里没有jpa了,只可以本身写实现.同时,想到以后可能会有切换天气api的需求,因此将逻辑封装到实现内,这里只返回一个weather对象:
public interface WeatherWebData { String serviceUrl="https://api.seniverse.com/v3/weather/daily.json?key=%s&location=%s&language=zh-Hans&unit=c&start=0&days=1"; public Weather getWeatherByLocation(String weatherKey, String location); }
将配置好的连接参数保存在接口内。
对于网络资源的访问选择了apache的http组件,因此赞成须要使用Maven进行引入:
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.5</version> </dependency>
而后在实现中完成对接口的访问:
@Repository public class WeatherWebDataImpl implements WeatherWebData { public Weather getWeatherByLocation(String weatherKey, String location) { try { HttpClient client = new DefaultHttpClient(); HttpUriRequest request=new HttpGet(String.format(this.serviceUrl,weatherKey,location)); request.setHeader("Content-type","application/json;charset=utf-8"); HttpResponse response= client.execute(request); String result = EntityUtils.toString(response.getEntity(), "utf-8"); System.out.println(result); } catch (Exception e) { e.printStackTrace(); } return null;
}
这段代码天生就适合进行提取方法的重构,因此在工具包内建立一个HttpUtil类,首先封装一下最简单get访问形式,返回String便可:
public class HttpUtil { public static String get(String url){ HttpClient client = new DefaultHttpClient(); HttpUriRequest request=new HttpGet(url); request.setHeader("Content-type","application/json;charset=utf-8"); HttpResponse response= null; String result = null; try { response = client.execute(request); result = EntityUtils.toString(response.getEntity(), "utf-8"); } catch (IOException e) { e.printStackTrace(); } return result; } }
此时先不考虑异常状况,实际状况下异常需前端配合,直接显示手动天气设置按钮。
而后在接口实现里替换掉便可:
String url=String.format(this.serviceUrl,weatherKey,location); String result= HttpUtil.get(url);
在进行对象建立以前,还要先看一下请求成功以外的状况,把随便给个非法的参数,好比用户key为空,看看返回状况:
{ "status":"The API key is invalid.", "status_code":"AP010003" }
格式不一致就好办了,能够经过判断status来判断返回的成功或者失败。
因为Weather的转换不具备广泛性,因此就不建立共有的工具类,在实现类中经过私有类来实现,String到对象的转换有不少种方法,好比以前刚刚用过的jackson,但这里因为实体类和json对象的属性并无一一对应,因此jackson就不那么特别适合。
那么有没有其余方法呢,答案固然是确定的,这里使用阿里出的fastjson,仍是同样的,经过maven进行引入:
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.46</version> </dependency>
他的使用很简单,就好像是mybatis同样,将一个对象以Map<String,Object>或List<Map<String,Object>>的形式返回,这样,只要咱们知道json的结构,就能够垂手可得的将它转换为任何形式的对象,这里即没啥好说了,直接贴方法代码:
:
private Weather jsonToWeather(String json){ Weather weather=new Weather(); Map<String,Object> map = JSON.parseObject(json); //判断失败 if(!map.containsKey("status")) { //正常状况 //weather是result节点的第一项 Map<String,Object> weatherMap= ((List<Map<String,Object>>)map.get("results")).get(0); Map<String, Object> locationJson = (Map<String, Object>) weatherMap.get("location"); weather.setName(locationJson.get("name").toString()); weather.setPath(locationJson.get("path").toString()); Map<String, Object> dailyJson = ((List<Map<String, Object>>) weatherMap.get("daily")).get(0); weather.setWeatherdate(dailyJson.get("date").toString()); weather.setCode_day(Integer.parseInt(dailyJson.get("code_day").toString())); weather.setText_day(dailyJson.get("text_day").toString()); weather.setTemp_high(Integer.parseInt(dailyJson.get("high").toString())); weather.setTemp_low(Integer.parseInt(dailyJson.get("low").toString())); weather.setWind_direction(dailyJson.get("wind_direction").toString()); weather.setWind_direction_degree(dailyJson.get("wind_direction_degree").toString()); weather.setWind_scale(dailyJson.get("wind_scale").toString()); weather.setWind_speed(dailyJson.get("wind_speed").toString()); weather.setPrecip(dailyJson.get("precip").toString()); weather.setIsweb(1); return weather; } return null; }
最终完成实现方法:
@Repository public class WeatherWebDataImpl implements WeatherWebData { public Weather getWeatherByLocation(String weatherKey, String location) { String url=String.format(this.serviceUrl,weatherKey,location); String result= HttpUtil.get(url); return jsonToWeather(result); } private Weather jsonToWeather(String json){ ...... } }
接下来是服务层,这层就没啥好说的了,接口定义了一个方法,经过地址查询天气:
public interface WeatherService { public Object weather(String address); }
而后实现稍微复杂一些,来统计一些实现需完成的操做:
接下来就一步一步完成这个服务层:
@Service public class WeatherServiceImpl implements WeatherService{ public Weather weather(String address) { return null; } }
注入天气持久层,并根据日期进行查询:
@Autowired private WeatherRepository weatherRepository; ..... public Weather weather(String address) { Weather weather=getWeatherByDb(address,(new SimpleDateFormat("yyyy-MM-dd")).format(new Date())); return weather; }
首先仍是引入字典持久层,而后封装一个查询key的私有方法(后期可能改成工具类),并放入缓存(暂时使用静态字段代替,后期使用Spring-Cache框架管理):
@Autowired private DictionaryItemRepository dictionaryItemRepository; private static String weatherKey=""; private String getWeatherKey(){ //缓存为空 if(WeatherServiceImpl.weatherKey.equals("")){ //查询字典表 List<DictionaryItem> dicList=dictionaryItemRepository.findByDicname("weatherKey"); if(dicList.size()>0) weatherKey= dicList.get(0).getDicvalue(); } return weatherKey; }
注入以前封装好的网络持久层,并继续增量代码:
@Autowired private WeatherWebData weatherWebData; ... public Weather weather(String address) { ... if(weather==null){ //若是没有,则查询,并存储到db 返回新内容 weather= weatherWebData.getWeatherByLocation(getWeatherKey(),address); } }
一样封装一个天气存储的方法,保存的同时还可获取db的自增ID:
private Weather saveWeather(Weather weather){ return weatherRepository.saveAndFlush(weather); }
最终,返回天气(接口方法完整代码):
public Weather weather(String address) { //查询db中是否有此日此地天气 Weather weather=getWeatherByDb(address,(new SimpleDateFormat("yyyy-MM-dd")).format(new Date())); if(weather==null){ //若是没有,则查询,并存储到db 返回新内容 weather= weatherWebData.getWeatherByLocation(getWeatherKey(),address); weather = saveWeather(weather); } return weather; }
因为操做均封装到了服务层,因此控制器已经尽量的薄了:
@RequestMapping(value = "/api/weather",method = RequestMethod.POST) public Object getWeather(HttpServletRequest request,@RequestBody Map map){ return result(weatherService.weather(map.get("address").toString())); }
后端折腾了一条线,终于要修改前端了,其实前端相对来讲修改的地方不多。
因为没有真机测试,因此如今只完成手动设置地点后天气获取
继续进入CreateOrShowDiaryItem.vue组件,修改设置地区的关闭按钮事件:
addressClose:function(event){ this.adddialog=false; //查询此地的天气 省市组合 this.searchWeather( this.addressProvince+""+this.addressCity); },
使用searchWeather方法进行服务器端查询:
searchWeather:function(address){ var data={ address:address }; this.$http.post("/api/weather",data,{headers:{"token":this.token}}).then(res=>{ if(res.data.msg!=""){ //使用手动天气设置 this.$store.commit('setWeatherIsShow',true); } var result=res.data.data; if(!(result== undefined ||result=="")){ //关闭手动设置按钮 this.$store.commit('setWeatherIsShow',false); this.weatherContent=result; this.weatherText= result.text_day+" "+result.temp_high+"度/"+result.temp_low+"度"; } },res=>{ //查询服务器失败,一样显示天气设定界面 this.$store.commit('setWeatherIsShow',true); }) }
最后,看看效果:
顺便和天气预报比对一下:
能够看到,已经获取到了实时天气。
本章代码(github):
客户端vue部分
服务端Java部分
谢谢观看
祝你们春节愉快,提早拜个早年