• 第一个微服务实现——天气预报服务
    • 开发环境
    • 数据来源
    • 初始化一个 Spring Boot 项目
    • 项目配置
    • 创建天气信息相关的值对象
    • 服务接口及实现
    • 控制器层
    • 配置类
    • 访问API
    • 源码

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

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

    开发环境

    • Gradle 4.0
    • Spring Boot 1.5.6
    • Apache HttpClient 4.5.3

    数据来源

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

    • 天气数据来源为中华万年历。例如:
      • 通过城市名字获得天气数据 :http://wthrcdn.etouch.cn/weather_mini?city=深圳
      • 通过城市id获得天气数据:http://wthrcdn.etouch.cn/weather_mini?citykey=101280601
    • 城市ID列表。每个城市都有一个唯一的ID作为标识。见 http://cj.weather.com.cn/support/Detail.aspx?id=51837fba1b35fe0f8411b6df 或者 http://mobile.weather.com.cn/js/citylist.xml。

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

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

    • https://waylau.com/change-gradle-wrapper-distribution-url-to-local-file/
    • https://waylau.com/use-maven-mirrors/

    项目配置

    添加 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 :

    • http://localhost:8080/weather/cityId/101280601
    • http://localhost:8080/weather/cityName/惠州

    能看到如下的数据返回

    weather-data

    源码

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