从一个天气预报系统讲起
本节通过Spring Boot技术快速实现一个天气预报系统。
通过这个系统,一方面可以了解Spring Boot的全面用法,为后续创建微服务应用打下基础;另一方面,该系统会作为本节进行微服务架构改造的非常好的起点。
下面以前面创建的hello-world应用作为基础进行改造,成为新的应用micro-weather-basic。
开发环境
为了演示本例,需要采用如下开发环境。
. JDK8。
.Gradle 4.0。
. Spring Boot Web Starter 2.0.0.M4。
Apache HttpClient 4.5.3。
数据来源
天气的数据是天气预报的实现基础。本应用与实际的天气数据无关,理论上可以兼容多种数据来源。但为求简单,我们在网上找了一个免费、可用的天气数据接口。
- ·天气数据来源为中华万年历。例如以下两种方式。
通过城市名称获得天气数据: http://wthrcdn.etouch.cn/weather_mini?city=深圳。
通过城市ID获得天气数据: http://wthrcdn.etouch.cn/weather_mini?citykey=101280601。
- ·城市ID列表。每个城市都有一个唯一的ID作为标识,见https:/waylau.com/data/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级]1>", "low" :"低温26℃", "fengxiang":"无持续风向", "type" :"阵雨" }, "date":"3日星期天", "high":"高温 29℃", "fengli":"<![CDATA[5-6级]1>", "low" :"低温26℃", "fengxiang":"无持续风向", "type":"大雨" "date":"4日星期一", "high":"高温29℃", "fengli":"<![CDATA[3-4级]1>", "low":"低温26℃", "fengxiang" :"西南风", "type":"暴雨" }, "date":"5日星期二", "high":"高温31℃", "fengli":"<![CDATA[<3级]]>", "low":"低温27℃", "fengxiang":"无持续风向", "type":"阵雨" "date" :"6日星期三", "high":"高温32℃", "fengli":"<![CDATA[<3级]l>", "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”,该项目可以直接以之前的“hello-world"应用作为基础进行修改。
添加Apache HttpClient的依赖,来作为Web请求的客户端。完整的依赖情况如下。
//依赖关系 dependencies { //该依赖用于编译阶段 compile('org.springframework.boot:spring-boot-starter-web') /添加Apache HttpClient依赖 compile('org.apache.httpcomponents:httpclient:4.5.3') //该依赖用于测试阶段 testCompile('org.springframework.boot:spring-boot-starter-test')}
创建天气信息相关的值对象
创建com.waylau.spring.cloud.weather.vo包,用于存放相关值对象。这些对象都是POJO对象,没有复杂的业务逻辑
创建天气信息类 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; 1/省略getter/setter方法 }
昨日天气信息类Yesterday :
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; //省略getter/setter方法 }
未来天气信息类Forecast:
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; //省略getter/setter方法 }
WeatherResponse作为整个消息的返回对象:
public class WeatherResponse implements Serializable{ private static final long serialversionUID =1L; private Weather data;1/消息数据 private String status;//消息状态 private string desc;/l消息描述 //省略getter/setter方法 }
服务接口及实现
创建com.waylau.spring.cloud.weather.service包,用于存放服务接口及其实现。
下面是定义服务的两个接口方法,一个是根据城市的ID来查询天气数据,另一个是根据城市名称来查询天气数据。
package com.waylau.spring.cloud.weather.service; import com.waylau.spring.cloud.weather.vo.WeatherResponse; /*★ *天气数据服务. 大 csince 1.0.o 2017年10月18日 * @author <a href="https://waylau.com">Way Lau</a> */ public interface weatherDataservice { /** 根据城市ID来查询天气数据 * *@param city工d *return */ WeatherResponse getDataByCityId(String cityId); /** *根据城市名称来查询天气数据 * *@param cityId *@return */ WeatherResponse getDataByCityName(String cityName); }
其服务实现WeatherDataServiceImpl为:
package com.waylau.spring.cloud.weather.service; import java.io.IOException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import com.fasterxml.jackson.databind.0bjectMapper; import com.waylau.spring.cloud.weather.vo.WeatherResponse; /** *天气数据服务. * * @since 1.0.0 2017年10月18日 * @author <a href="https://waylau.com">Way Lau</a> */ @service public class WeatherDataServiceImpl implements WeatherDataService { Autowired private RestTemplate restTemplate; private final string WEATHER_API = "http://wthrcdn.etouch.cn/weath- er_ 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 (工OException e){ e.printStackTrace(); return weather;
其中:
. RestTemplate是一个REST客户端,默认采用Apache HttpClient来实现;
·返回的天气信息采用了Jackson来进行反序列化,使其成为WeatherResponse对象。
控制器层
创建com.waylau.spring.cloud.weather.service包,用于存放控制器层代码。控制器层暴露了RESTful API接口。
package com.waylau.spring.cloud.weather.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping: import org.springframework.web.bind.annotation.RestController; import com.waylau.spring.cloud.weather.service.WeatherDataService; import com.waylau.spring.cloud.weather.vo.WeatherResponse; /★大 *天气AP工. * * @since 1.0.0 2017年10月18日 * author <a href="https://waylau.com">Way Lau</a> */ @RestController RequestMapping("/weather") public class WeatherController { @Autowired private WeatherDataService weatherDataService; @GetMapping("/cityId/{cityId}") public WeatherResponse getReportByCityId(CPathVariable("cityId") string cityId){ return weatherDataService.getDataByCityId(cityId); } GetMapping("/cityName/{cityName}") public WeatherResponse getReportByCityName (CPathVariable ("cityName") string cityName){ return weatherDataService.getDataByCityName(cityName);}
其中,@RestController会自动将返回的数据进行序列化,使其成为JSON数据格式。
配置类
创建com.waylau.spring.cloud.weather.config包,用于存放配置相关的代码。创建RestConfiguration
类,该类是RestTemplate 的配置类。
package com.waylau.spring.cloud.weather.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; /** *REST 配置类. * *@since 1.0.02017年10月18日 * @author <a href="https://waylau.com" >Way Lau</a> */ Configuration public class RestConfiguration { Autowired private RestTemplateBuilder builder; @Bean public RestTemplate restTemplate({ return builder .build();} }
访问API
运行项目之后,访问以下API来进行测试。
- . http://localhost:8080/weather/cityId/101280601。
- http://localhost:8080/weather/cityName/惠州。
能看到如图6-1所示的天气API返回的数据。