基於Spring Boot的天氣預報服務
本文,我們將基於 Spring Boot 技術來實現一個微服務天氣預報服務接口——micro-weather-basic。micro-weather-basic 的作用是實現簡單的天氣預報功能,可以根據不同的城市,查詢該城市的實時天氣情況。
開發環境
- Gradle 4.0
- Spring Boot 1.5.6
- Apache HttpClient 1.5.3
數據來源
理論上,天氣的數據是天氣預報的實現基礎。本應用與實際的天氣數據無關,理論上,可以兼容多種數據來源。但為求簡單,我們在網上找了一個免費、可用的天氣數據接口。
- 天氣數據來源為中華萬年曆。例如:
- 通過城市名字獲得天氣數據 :https://wthrcdn.etouch.cn/weather_mini?city=深圳
- 通過城市id獲得天氣數據:https://wthrcdn.etouch.cn/weather_mini?citykey=101280601
- 城市ID列表。每個城市都有一個唯一的ID作為標識。見 https://cj.weather.com.cn/support/Detail.aspx?id=51837fba1b35fe0f8411b6df 或者 https://mobile.weather.com.cn/js/citylist.xml。
調用天氣服務接口示例,我們以“深圳”城市為例,可用看到如下天氣數據返回。
{
"data": {
"yesterday": {
"date": "1日星期五",
"high": "高溫 33℃",
"fx": "無持續風向",
"low": "低溫 26℃",
"fl": "<![CDATA[<3級]]>",
"type": "多雲"
},
"city": "深圳",
"aqi": "72",
"forecast": [
{
"date": "2日星期六",
"high": "高溫 32℃",
"fengli": "<![CDATA[<3級]]>",
"low": "低溫 26℃",
"fengxiang": "無持續風向",
"type": "陣雨"
},
{
"date": "3日星期天",
"high": "高溫 29℃",
"fengli": "<![CDATA[5-6級]]>",
"low": "低溫 26℃",
"fengxiang": "無持續風向",
"type": "大雨"
},
{
"date": "4日星期一",
"high": "高溫 29℃",
"fengli": "<![CDATA[3-4級]]>",
"low": "低溫 26℃",
"fengxiang": "西南風",
"type": "暴雨"
},
{
"date": "5日星期二",
"high": "高溫 31℃",
"fengli": "<![CDATA[<3級]]>",
"low": "低溫 27℃",
"fengxiang": "無持續風向",
"type": "陣雨"
},
{
"date": "6日星期三",
"high": "高溫 32℃",
"fengli": "<![CDATA[<3級]]>",
"low": "低溫 27℃",
"fengxiang": "無持續風向",
"type": "陣雨"
}
],
"ganmao": "風較大,陰冷潮濕,較易發生感冒,體質較弱的朋友請注意適當防護。",
"wendu": "29"
},
"status": 1000,
"desc": "OK"
}
我們通過觀察數據,來了解每個返回字段的含義。
- "city": 城市名稱
- "aqi": 空氣指數,
- "wendu": 實時溫度
- "date": 日期,包含未來5天
- "high":最高溫度
- "low": 最低溫度
- "fengli": 風力
- "fengxiang": 風向
- "type": 天氣類型
以上數據,是我們需要的天氣數據的核心數據,但是,同時也要關注下麵兩個字段:
- "status": 接口調用的返回狀態,返回值“1000”,意味著數據是接口正常
- "desc": 接口狀態的描述,“OK”代表接口正常
重點關注返回值不是“1000”的情況,說明,這個接口調用異常了。
初始化一個 Spring Boot 項目
初始化一個 Spring Boot 項目 micro-weather-basic
,該項目可以直接在我們之前章節課程中的 basic-gradle 項目基礎進行修改。同時,為了優化項目的構建速度,我們對Maven中央倉庫地址和 Gradle Wrapper 地址做了調整。其中細節暫且不表,讀者可以自行參閱源碼,或者學習筆者所著的《Spring Boot 教程》(https://github.com/waylau/spring-boot-tutorial)。其原理,我也整理到我的博客中了:
- https://waylau.com/change-gradle-wrapper-distribution-url-to-local-file/
- https://waylau.com/use-maven-mirrors/
項目配置
添加 Apache HttpClient 的依賴,來作為我們Web請求的客戶端。
// 依賴關係
dependencies {
//...
// 添加 Apache HttpClient 依賴
compile('org.apache.httpcomponents:httpclient:4.5.3')
//...
}
創建天氣信息相關的值對象
創建com.waylau.spring.cloud.vo
包,用於相關值對象。創建天氣信息類 Weather
public class Weather implements Serializable {
private static final long serialVersionUID = 1L;
private String city;
private String aqi;
private String wendu;
private String ganmao;
private Yesterday yesterday;
private List<Forecast> forecast;
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getAqi() {
return aqi;
}
public void setAqi(String aqi) {
this.aqi = aqi;
}
public String getWendu() {
return wendu;
}
public void setWendu(String wendu) {
this.wendu = wendu;
}
public String getGanmao() {
return ganmao;
}
public void setGanmao(String ganmao) {
this.ganmao = ganmao;
}
public Yesterday getYesterday() {
return yesterday;
}
public void setYesterday(Yesterday yesterday) {
this.yesterday = yesterday;
}
public List<Forecast> getForecast() {
return forecast;
}
public void setForecast(List<Forecast> forecast) {
this.forecast = forecast;
}
}
昨日天氣信息:
public class Yesterday implements Serializable {
private static final long serialVersionUID = 1L;
private String date;
private String high;
private String fx;
private String low;
private String fl;
private String type;
public Yesterday() {
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getHigh() {
return high;
}
public void setHigh(String high) {
this.high = high;
}
public String getFx() {
return fx;
}
public void setFx(String fx) {
this.fx = fx;
}
public String getLow() {
return low;
}
public void setLow(String low) {
this.low = low;
}
public String getFl() {
return fl;
}
public void setFl(String fl) {
this.fl = fl;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
未來天氣信息:
public class Forecast implements Serializable {
private static final long serialVersionUID = 1L;
private String date;
private String high;
private String fengxiang;
private String low;
private String fengli;
private String type;
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getHigh() {
return high;
}
public void setHigh(String high) {
this.high = high;
}
public String getFengxiang() {
return fengxiang;
}
public void setFengxiang(String fengxiang) {
this.fengxiang = fengxiang;
}
public String getLow() {
return low;
}
public void setLow(String low) {
this.low = low;
}
public String getFengli() {
return fengli;
}
public void setFengli(String fengli) {
this.fengli = fengli;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Forecast() {
}
}
WeatherResponse 作為整個消息的返回對象
public class WeatherResponse implements Serializable {
private static final long serialVersionUID = 1L;
private Weather data; // 消息數據
private String status; // 消息狀態
private String desc; // 消息描述
public Weather getData() {
return data;
}
public void setData(Weather data) {
this.data = data;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
服務接口及實現
定義了獲取服務的兩個接口方法
public interface WeatherDataService {
/**
* 根據城市ID查詢天氣數據
* @param cityId
* @return
*/
WeatherResponse getDataByCityId(String cityId);
/**
* 根據城市名稱查詢天氣數據
* @param cityId
* @return
*/
WeatherResponse getDataByCityName(String cityName);
}
其實現為:
@Service
public class WeatherDataServiceImpl implements WeatherDataService {
@Autowired
private RestTemplate restTemplate;
private final String WEATHER_API = "https://wthrcdn.etouch.cn/weather_mini";
@Override
public WeatherResponse getDataByCityId(String cityId) {
String uri = WEATHER_API + "?citykey=" + cityId;
return this.doGetWeatherData(uri);
}
@Override
public WeatherResponse getDataByCityName(String cityName) {
String uri = WEATHER_API + "?city=" + cityName;
return this.doGetWeatherData(uri);
}
private WeatherResponse doGetWeatherData(String uri) {
ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class);
String strBody = null;
if (response.getStatusCodeValue() == 200) {
strBody = response.getBody();
}
ObjectMapper mapper = new ObjectMapper();
WeatherResponse weather = null;
try {
weather = mapper.readValue(strBody, WeatherResponse.class);
} catch (IOException e) {
e.printStackTrace();
}
return weather;
}
}
返回的天氣信息采用了 Jackson 來進行反序列化成為 WeatherResponse 對象。
控製器層
控製器層暴露了RESTful API 地址。
@RestController
@RequestMapping("/weather")
public class WeatherController {
@Autowired
private WeatherDataService weatherDataService;
@GetMapping("/cityId/{cityId}")
public WeatherResponse getReportByCityId(@PathVariable("cityId") String cityId) {
return weatherDataService.getDataByCityId(cityId);
}
@GetMapping("/cityName/{cityName}")
public WeatherResponse getReportByCityName(@PathVariable("cityName") String cityName) {
return weatherDataService.getDataByCityName(cityName);
}
}
@RestController
自動會將返回的數據,序列化成 JSON數據格式。
配置類
RestConfiguration 是 RestTemplate 的配置類。
@Configuration
public class RestConfiguration {
@Autowired
private RestTemplateBuilder builder;
@Bean
public RestTemplate restTemplate() {
return builder.build();
}
}
訪問API
運行項目之後,訪問項目的 API :
能看到如下的數據返回
源碼
本章節的源碼,見 https://github.com/waylau/spring-cloud-tutorial/ samples
目錄下的micro-weather-basic
。
參考引用
最後更新:2017-09-06 00:04:04