若是你想開發一個應用(1-20)

天氣api

上一章裏咱們已經能夠手動設置天氣狀況,但在通常狀況下,天氣狀況都是客觀的,因此他不該該由人手動設置。因此讀取天氣接口自動獲取就是一個必須的功能點了。前端

天氣預報的接口有不少,最先的weather.cn有時好時壞,因此最終選擇了心知天氣的接口。vue

這個接口的免費版能夠支持國內市級的幾乎全部城市,這也是我在上一章把選擇地區的精確度定爲市級的緣由之一。而且能夠根據名稱和座標等功能獲取實時天氣,當前階段,免費版也能夠支持現有的功能.java

心知天氣的用法很簡單,首先註冊一個帳號,而後就回有一個key,接下來將key嵌入到url中就能夠經過webapi的方式get回一個json的字符串,解析便可。git

key的保存方式

爲了將來程序的擴展性和保密性,天氣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來判斷返回的成功或者失敗。

JSON解析

因爲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);
}

而後實現稍微複雜一些,來統計一些實現需完成的操做:

  1. 查詢緩存內是否已有今天此地的天氣,若有直接返回
  2. 經過字典表查詢心知天氣的api所需key
  3. 調用天氣資源,查詢此地天氣
  4. 將返回天氣存入db
  5. 返回天氣

接下來就一步一步完成這個服務層:

@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;
}

經過字典表查詢心知天氣的api所需key

首先仍是引入字典持久層,而後封裝一個查詢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

一樣封裝一個天氣存儲的方法,保存的同時還可獲取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部分

謝謝觀看

祝你們春節愉快,提早拜個早年

相關文章
相關標籤/搜索