上一章裏咱們已經能夠手動設置天氣狀況,但在通常狀況下,天氣狀況都是客觀的,因此他不該該由人手動設置。因此讀取天氣接口自動獲取就是一個必須的功能點了。前端
天氣預報的接口有不少,最先的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部分
謝謝觀看
祝你們春節愉快,提早拜個早年