第一个微服务实现——天气预报服务

本章节,我们将基于 Spring Boot 技术来实现我们的第一个微服务天气预报应用——micro-weather-basic。micro-weather-basic 的作用是实现简单的天气预报功能,可以根据不同的城市,查询该城市的实时天气情况。

开发环境

  • Gradle 4.0
  • Spring Boot 1.5.6
  • Apache HttpClient 4.5.3

数据来源

理论上,天气的数据是天气预报的实现基础。本应用与实际的天气数据无关,理论上,可以兼容多种数据来源。但为求简单,我们在网上找了一个免费、可用的天气数据接口。

调用天气服务接口示例,我们以“深圳”城市为例,可用看到如下天气数据返回。

  1. {
  2. "data": {
  3. "yesterday": {
  4. "date": "1日星期五",
  5. "high": "高温 33℃",
  6. "fx": "无持续风向",
  7. "low": "低温 26℃",
  8. "fl": "<![CDATA[<3级]]>",
  9. "type": "多云"
  10. },
  11. "city": "深圳",
  12. "aqi": "72",
  13. "forecast": [
  14. {
  15. "date": "2日星期六",
  16. "high": "高温 32℃",
  17. "fengli": "<![CDATA[<3级]]>",
  18. "low": "低温 26℃",
  19. "fengxiang": "无持续风向",
  20. "type": "阵雨"
  21. },
  22. {
  23. "date": "3日星期天",
  24. "high": "高温 29℃",
  25. "fengli": "<![CDATA[5-6级]]>",
  26. "low": "低温 26℃",
  27. "fengxiang": "无持续风向",
  28. "type": "大雨"
  29. },
  30. {
  31. "date": "4日星期一",
  32. "high": "高温 29℃",
  33. "fengli": "<![CDATA[3-4级]]>",
  34. "low": "低温 26℃",
  35. "fengxiang": "西南风",
  36. "type": "暴雨"
  37. },
  38. {
  39. "date": "5日星期二",
  40. "high": "高温 31℃",
  41. "fengli": "<![CDATA[<3级]]>",
  42. "low": "低温 27℃",
  43. "fengxiang": "无持续风向",
  44. "type": "阵雨"
  45. },
  46. {
  47. "date": "6日星期三",
  48. "high": "高温 32℃",
  49. "fengli": "<![CDATA[<3级]]>",
  50. "low": "低温 27℃",
  51. "fengxiang": "无持续风向",
  52. "type": "阵雨"
  53. }
  54. ],
  55. "ganmao": "风较大,阴冷潮湿,较易发生感冒,体质较弱的朋友请注意适当防护。",
  56. "wendu": "29"
  57. },
  58. "status": 1000,
  59. "desc": "OK"
  60. }

我们通过观察数据,来了解每个返回字段的含义。

  • “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)。其原理,我也整理到我的博客中了:

项目配置

添加 Apache HttpClient 的依赖,来作为我们Web请求的客户端。

  1. // 依赖关系
  2. dependencies {
  3. //...
  4. // 添加 Apache HttpClient 依赖
  5. compile('org.apache.httpcomponents:httpclient:4.5.3')
  6. //...
  7. }

创建天气信息相关的值对象

创建com.waylau.spring.cloud.vo包,用于相关值对象。创建天气信息类 Weather

  1. public class Weather implements Serializable {
  2. private static final long serialVersionUID = 1L;
  3. private String city;
  4. private String aqi;
  5. private String wendu;
  6. private String ganmao;
  7. private Yesterday yesterday;
  8. private List<Forecast> forecast;
  9. public String getCity() {
  10. return city;
  11. }
  12. public void setCity(String city) {
  13. this.city = city;
  14. }
  15. public String getAqi() {
  16. return aqi;
  17. }
  18. public void setAqi(String aqi) {
  19. this.aqi = aqi;
  20. }
  21. public String getWendu() {
  22. return wendu;
  23. }
  24. public void setWendu(String wendu) {
  25. this.wendu = wendu;
  26. }
  27. public String getGanmao() {
  28. return ganmao;
  29. }
  30. public void setGanmao(String ganmao) {
  31. this.ganmao = ganmao;
  32. }
  33. public Yesterday getYesterday() {
  34. return yesterday;
  35. }
  36. public void setYesterday(Yesterday yesterday) {
  37. this.yesterday = yesterday;
  38. }
  39. public List<Forecast> getForecast() {
  40. return forecast;
  41. }
  42. public void setForecast(List<Forecast> forecast) {
  43. this.forecast = forecast;
  44. }
  45. }

昨日天气信息:

  1. public class Yesterday implements Serializable {
  2. private static final long serialVersionUID = 1L;
  3. private String date;
  4. private String high;
  5. private String fx;
  6. private String low;
  7. private String fl;
  8. private String type;
  9. public Yesterday() {
  10. }
  11. public String getDate() {
  12. return date;
  13. }
  14. public void setDate(String date) {
  15. this.date = date;
  16. }
  17. public String getHigh() {
  18. return high;
  19. }
  20. public void setHigh(String high) {
  21. this.high = high;
  22. }
  23. public String getFx() {
  24. return fx;
  25. }
  26. public void setFx(String fx) {
  27. this.fx = fx;
  28. }
  29. public String getLow() {
  30. return low;
  31. }
  32. public void setLow(String low) {
  33. this.low = low;
  34. }
  35. public String getFl() {
  36. return fl;
  37. }
  38. public void setFl(String fl) {
  39. this.fl = fl;
  40. }
  41. public String getType() {
  42. return type;
  43. }
  44. public void setType(String type) {
  45. this.type = type;
  46. }
  47. }

未来天气信息:

  1. public class Forecast implements Serializable {
  2. private static final long serialVersionUID = 1L;
  3. private String date;
  4. private String high;
  5. private String fengxiang;
  6. private String low;
  7. private String fengli;
  8. private String type;
  9. public String getDate() {
  10. return date;
  11. }
  12. public void setDate(String date) {
  13. this.date = date;
  14. }
  15. public String getHigh() {
  16. return high;
  17. }
  18. public void setHigh(String high) {
  19. this.high = high;
  20. }
  21. public String getFengxiang() {
  22. return fengxiang;
  23. }
  24. public void setFengxiang(String fengxiang) {
  25. this.fengxiang = fengxiang;
  26. }
  27. public String getLow() {
  28. return low;
  29. }
  30. public void setLow(String low) {
  31. this.low = low;
  32. }
  33. public String getFengli() {
  34. return fengli;
  35. }
  36. public void setFengli(String fengli) {
  37. this.fengli = fengli;
  38. }
  39. public String getType() {
  40. return type;
  41. }
  42. public void setType(String type) {
  43. this.type = type;
  44. }
  45. public Forecast() {
  46. }
  47. }

WeatherResponse 作为整个消息的返回对象

  1. public class WeatherResponse implements Serializable {
  2. private static final long serialVersionUID = 1L;
  3. private Weather data; // 消息数据
  4. private String status; // 消息状态
  5. private String desc; // 消息描述
  6. public Weather getData() {
  7. return data;
  8. }
  9. public void setData(Weather data) {
  10. this.data = data;
  11. }
  12. public String getStatus() {
  13. return status;
  14. }
  15. public void setStatus(String status) {
  16. this.status = status;
  17. }
  18. public String getDesc() {
  19. return desc;
  20. }
  21. public void setDesc(String desc) {
  22. this.desc = desc;
  23. }
  24. }

服务接口及实现

定义了获取服务的两个接口方法

  1. public interface WeatherDataService {
  2. /**
  3. * 根据城市ID查询天气数据
  4. * @param cityId
  5. * @return
  6. */
  7. WeatherResponse getDataByCityId(String cityId);
  8. /**
  9. * 根据城市名称查询天气数据
  10. * @param cityId
  11. * @return
  12. */
  13. WeatherResponse getDataByCityName(String cityName);
  14. }

其实现为:

  1. @Service
  2. public class WeatherDataServiceImpl implements WeatherDataService {
  3. @Autowired
  4. private RestTemplate restTemplate;
  5. private final String WEATHER_API = "http://wthrcdn.etouch.cn/weather_mini";
  6. @Override
  7. public WeatherResponse getDataByCityId(String cityId) {
  8. String uri = WEATHER_API + "?citykey=" + cityId;
  9. return this.doGetWeatherData(uri);
  10. }
  11. @Override
  12. public WeatherResponse getDataByCityName(String cityName) {
  13. String uri = WEATHER_API + "?city=" + cityName;
  14. return this.doGetWeatherData(uri);
  15. }
  16. private WeatherResponse doGetWeatherData(String uri) {
  17. ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class);
  18. String strBody = null;
  19. if (response.getStatusCodeValue() == 200) {
  20. strBody = response.getBody();
  21. }
  22. ObjectMapper mapper = new ObjectMapper();
  23. WeatherResponse weather = null;
  24. try {
  25. weather = mapper.readValue(strBody, WeatherResponse.class);
  26. } catch (IOException e) {
  27. e.printStackTrace();
  28. }
  29. return weather;
  30. }
  31. }

返回的天气信息采用了 Jackson 来进行反序列化成为 WeatherResponse 对象。

控制器层

控制器层暴露了RESTful API 地址。

  1. @RestController
  2. @RequestMapping("/weather")
  3. public class WeatherController {
  4. @Autowired
  5. private WeatherDataService weatherDataService;
  6. @GetMapping("/cityId/{cityId}")
  7. public WeatherResponse getReportByCityId(@PathVariable("cityId") String cityId) {
  8. return weatherDataService.getDataByCityId(cityId);
  9. }
  10. @GetMapping("/cityName/{cityName}")
  11. public WeatherResponse getReportByCityName(@PathVariable("cityName") String cityName) {
  12. return weatherDataService.getDataByCityName(cityName);
  13. }
  14. }

@RestController自动会将返回的数据,序列化成 JSON数据格式。

配置类

RestConfiguration 是 RestTemplate 的配置类。

  1. @Configuration
  2. public class RestConfiguration {
  3. @Autowired
  4. private RestTemplateBuilder builder;
  5. @Bean
  6. public RestTemplate restTemplate() {
  7. return builder.build();
  8. }
  9. }

访问API

运行项目之后,访问项目的 API :

能看到如下的数据返回

weather-data

源码

本章节的源码,在micro-weather-basic目录下。