【项目实践】在系统接入天气api,根据当前天气提醒,做好plan

前期调研

经多方查找,发现和风天气对于个人开发人相对有好,有详细的api接口文档以及实现案例。

优点:

  • 只需要提供城市、授权ID和Key,就会进行返回 跟天气相关的多样态数据;
  • 响应数据种类多,提供各类svg图标样式,后续扩展性可以延伸;

详情查看官网

准备工作

注册和风天气账号

  1. 注册时需要准备邮箱,手机账号
  2. 验证码注意检查下垃圾邮件,有可能被默认规则拦截
  1. 添加项目,这里就开始注册需要使用 的api了
  1. 注册项目
  1. 创建完成后会显示这个,下面的凭据 点击;
  2. api Key 就是我们需要用的APIKey
  3. 详细设置参照官方文档进行:

开发配置

开始实行

导入依赖

  1. 返回JSON数据,所以数据格式转换是必须的,需要jaskson
  2. 和风天气 API响应的数据使用Gzip压缩,正常显示失败,需要设置 HttpComponentsClientHttpRequestFactory ,为了解决 API 响应压缩导致的解析问题,确保能够正确处理和风天气 API 返回的可能经过 gzip 压缩的 JSON 数据。
xml 复制代码
        <!-- Jackson(解析JSON) -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.13.4.2</version>
        </dependency>
        
        <!-- HttpClient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
        </dependency>

bean注入需要扫描的包

  1. 我将所有的外置工具都放到utility中;
  2. 配置RestTemplate(用于发送HTTP请求)
xml 复制代码
    <!-- 扫描Controller和Service所在的包 -->
    <context:component-scan base-package="com.utility"/>
    
    <!-- 配置RestTemplate(用于发送HTTP请求) -->
    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate"/>

目录如下:

配置秘钥

java 复制代码
public class WeatherConfig {
    // 和风天气API配置
    public static final String API_URL = "https://devapi.qweather.com/v7/weather/now";
    public static final String API_KEY = "你的密钥"; // 替换为实际密钥
    public static final String CITY_ID = "101010100"; // 北京
}

编写实体类

  1. 返回显示的数据,应答代码所需数据
java 复制代码
// 顶层响应
public class WeatherResponse {
    private String code;
    private Now now;
    // getter + setter
}
java 复制代码
// 当前天气详情
public class Now {
    private String temp;
    private String text;
    private String windDir;
    private String humidity;
    // getter + setter
}

编写逻辑层代码

  1. 调用和风天气API
  2. 处理数据,符合URL规范,参考官方文档进行
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.weather.config.WeatherConfig;

@Service
public class WeatherService {

    @Autowired
    private RestTemplate restTemplate;

    // 获取当前天气
    public WeatherResponse getCurrentWeather() {
        // 拼接请求URL
        String url = String.format("%s?location=%s&key=%s",
                WeatherConfig.API_URL,
                WeatherConfig.CITY_ID,
                WeatherConfig.API_KEY);
        // 调用API并返回结果
        return restTemplate.getForObject(url, WeatherResponse.class);
    }
}

控制层代码

  1. 返回视图解析器,返回页面显示详情信息
  2. 返回ajax,动态加载页面,在页面小地方加载内容
java 复制代码
@Controller
@RequestMapping("/weather")
public class WeatherController {

    @Autowired
    private WeatherService weatherService;

    @Autowired
    private IpParseService ipParseService;

    // 响应JSON格式的天气数据
    @GetMapping("/current/json")
    @ResponseBody // 表示返回JSON而非视图
    public WeatherResponse getCurrentWeather(HttpServletRequest request) {
        String city = ipParseService.getCityByIp(IpUtils.getRealIp(request));
        WeatherResponse weather = weatherService.getCurrentWeather(city);
        weather.setLocation(city);
        return weather;
    }

    @GetMapping("/current")
    public String getCurrentWeather(HttpServletRequest request, Model mode) {
        String city = ipParseService.getCityByIp(IpUtils.getRealIp(request));
        WeatherResponse weather = weatherService.getCurrentWeather(city);
        weather.setLocation(city);
        mode.addAttribute("weather", weather);
        return "weather";
    }

View层视图显示

  1. 动态加载的 使用ajax
html 复制代码
       $.ajax({
            url: "/weather/current/json",
            type: "GET",
            dataType: "json",
            success: function (data) {
                console.log("天气数据:", data);
                if (data && data.now !== null) {
                    //     $("#weather_info").text(data.now.text);
                    $("#weather-info").html("天气:" + data.now.text);
                }
            },
            error: function (xhr, status, error) {
                console.log("获取天气数据失败:", error);
                $("#weather-info").html("天气:获取失败");
            }
        })
  1. 视图跳转,显示视图的内容
html 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
    <title>当前天气</title>
    <style>
        .weather-container {
            width: 500px;
            margin: 50px auto;
            padding: 20px;
            border: 1px solid #ddd;
            border-radius: 10px;
            text-align: center;
        }

        .weather-info {
            margin: 20px 0;
            font-size: 18px;
        }

        .temp {
            font-size: 36px;
            color: #2c3e50;
            margin: 10px 0;
        }

        .text {
            font-size: 24px;
            color: #3498db;
        }
    </style>
</head>
<body>
<div class="weather-container">
    <h2>当前天气信息</h2>

    <!-- 显示天气数据 -->
    <c:if test="${not empty weather}">
        <div class="weather-info">
            <div class="text">${weather.location}</div>
            <div class="text">${weather.now.text}</div>
            <div class="temp">${weather.now.temp} ℃</div>
            <div>风向:${weather.now.windDir}</div>
            <div>湿度:${weather.now.humidity}%</div>
        </div>
    </c:if>

    <!-- 错误提示 -->
    <c:if test="${empty weather or weather.code != '200'}">
        <div style="color: red;">获取天气失败,请稍后重试</div>
    </c:if>
</div>
</body>
</html>

优化

优化点1:根据登录ip判断所在地,自动使用所在地地点显示天气

  1. 工具调查,需要使用获取地址的api工具
  2. 需要用到api: [https://ip.taobao.com/service/getIpInfo.php](https://ip.taobao.com/service/getIpInfo.php),现在已经永久废止了,使用[https://ip9.com.cn/get?ip=[IP](https://ip9.com.cn/get?ip=[IP)地址]与前面一样,都是会返回JSON格式
控制器

Controller 改动原来的就可以,毕竟是为了保障以后

逻辑层
  1. 所需数据少,需要变动点少,索性放在一个里面,引入lombok,省下写getter/setter;
  2. 城市 是需要请求天气所需要的东西,如果进行变动的话需要考虑进去
  3. 请求响应失败的场合 需要返回个默认地址
java 复制代码
package com.utility.service;

import com.utility.config.WeatherConfig;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class CityCodeService {

    // 和风天气城市搜索API  curl "https://nj59fbrfwr.re.qweatherapi.com/geo/v2/city/lookup?location=北京&key=key***"
    private static final String CITY_SEARCH_URL = WeatherConfig.API_URL + "/geo/v2/city/lookup";

    
    //    @Value("${weather.api.key}")
    //    private String apiKey;

    @Autowired
    private RestTemplate restTemplate;

    // 根据城市名获取城市ID(如"北京"→"101010100")
    public String getCityIdByCityName(String cityName) {
        try {
            // 拼接请求URL
            String url = String.format("%s?location=%s&key=%s",
                                       CITY_SEARCH_URL, cityName, WeatherConfig.API_KEY);

            System.out.println(" 和风天气城市搜索API::" + url);

            // 调用API并解析结果
            CitySearchResponse response = restTemplate.getForObject(url, CitySearchResponse.class);

            if (response != null && "200".equals(response.getCode())
                && response.getLocation() != null && !response.getLocation().isEmpty()) {
                return response.getLocation().get(0).getId(); // 返回第一个匹配的城市ID
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 失败时返回默认城市ID : 北京
        return "101010100";
    }

    @Data
    // 城市搜索API返回的实体类(简化版)
    public static class CitySearchResponse {
        private String code; // 200表示成功
        private java.util.List<Location> location;
    }

    @Data
    public static class Location {
        private String id; // 城市ID
    }
}
  1. taobao的废止了,使用了ip9的接口去用;IpParseService返回一个城市name,这个nameCityCodeService需要使用,我们使用这个去 查找城市代码;
  2. Test 成功,使用junit测试通过,使用的百度在北京的服务器所在的ip地址;使用单元测试的思想,可以避免我们不停的重复启停服务所花费时间,来回启动会导致 IDE工具 内存不足;
java 复制代码
package com.utility.service;

import lombok.Data;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class IpParseService {

    // 淘宝IP解析接口
//    private static final String IP_API_URL = "https://ip.taobao.com/service/getIpInfo.php?ip=";
    private static final String IP_API_URL = "https://ip9.com.cn/get?ip=";

    @Autowired
    private RestTemplate restTemplate;

    // 根据IP获取城市名称(如"北京")
    public String getCityByIp(String ip) {
        try {
            // 调用淘宝IP接口
            String url = IP_API_URL + ip;

            // 获取原始响应作为字符串
//            String responseString = restTemplate.getForObject(url, String.class);

            IpResponse response = restTemplate.getForObject(url, IpResponse.class);

            // 手动解析JSON(需要引入Jackson或其他JSON库)
//            ObjectMapper objectMapper = new ObjectMapper();
//            IpResponse response = objectMapper.readValue(responseString, IpResponse.class);

            // 解析返回结果(淘宝接口格式特殊,需对应实体类)
            if (response != null && response.getCode() == 0) {
                if (response.getData() != null && !response.getData().getCity().isEmpty()) {
                    return response.getData().getCity();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 解析失败时返回默认城市
        return "山东";
    }

    @Data
    // 淘宝IP接口返回的实体类(简化版)
    public static class IpResponse {
        private int code; // 0表示成功
        private IpData data;
    }

    @Data
    public static class IpData {
        private String city; // 城市名称
    }

    @Test
    public void test() {
        RestTemplate restTemplate = new RestTemplate();
        String IP = "182.61.200.108";
        System.out.println(getCityByIp(IP));
    }
}
工具类
  1. 解析Request中ip地址
java 复制代码
package com.utility.utils;

import javax.servlet.http.HttpServletRequest;

public class IpUtils {
    // 获取用户真实IP(考虑代理情况)
    public static String getRealIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // 多代理情况下,取第一个IP
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        return ip;
    }
}

优化点2:地址拆出来,方便以后使用,这样就可以一次请求存在Session中使用,节省api请求次数,提高效率

控制器

Controller 改动原来的就可以,毕竟是为了保障以后

逻辑层

原来功能:

java 复制代码
public WeatherResponse getCurrentWeather(HttpServletRequest request) {

String cityId =
cityCodeService
.getCityIdByCityName(ipParseService
                     .getCityByIp(IpUtils.getRealIp(request)));

}

分析可拆分点,IP转化日期单独拿出,后面再单独去调用;

java 复制代码
public WeatherResponse getCurrentWeather(String city) {
    String cityId = cityCodeService.getCityIdByCityName(city);
}
实体类

需求发生变化,页面内容就要变更,取值的方式就多种多样了,改之前的Entity,返回的ResponseVo中加个字段? 再新写个实体类? 把IpParseServiceIpData提取出来?思路各色各样

人世间万事万物都是变得,唯唯物辩证法永恒。

相关推荐
Lemon程序馆1 分钟前
Mysql 常见的性能分析手段
数据库·后端·mysql
东阳马生架构3 分钟前
Dubbo源码—1.服务发布的主要流程
后端
meiguiyulu11 分钟前
深入理解 CountdownLatch:多线程同步的得力助手
后端
种子q_q12 分钟前
synchronized的原理以及锁升级机制
后端·面试
林太白13 分钟前
mars3d使用
前端·javascript·后端
wtsolutions34 分钟前
免费MCP: JSON 转 Excel MCP
json·excel·api·csv·mcp·wtsolutions
XingYuyu_Coder39 分钟前
(JAVA)自建应用调用企业微信API接口,设置企业可信IP
java·tcp/ip·企业微信
这里有鱼汤40 分钟前
发现一个不错的库推荐给大家:DuckDB + Python + Pandas 的组合,量化研究利器 🚀
数据库·后端
shark_chili44 分钟前
浅谈java并发编程中等待通知模型的哲学
后端
慕y2741 小时前
Java学习第一百零一部分——网关(Gateway)
java·网络·学习