前期调研
经多方查找,发现和风天气对于个人开发人相对有好,有详细的api接口文档以及实现案例。
优点:
- 只需要提供城市、授权ID和Key,就会进行返回 跟天气相关的多样态数据;
- 响应数据种类多,提供各类svg图标样式,后续扩展性可以延伸;

详情查看官网
准备工作
注册和风天气账号
- 注册时需要准备邮箱,手机账号
- 验证码注意检查下垃圾邮件,有可能被默认规则拦截

- 添加项目,这里就开始注册需要使用 的api了

- 注册项目

- 创建完成后会显示这个,下面的凭据 点击;
- api Key 就是我们需要用的APIKey
- 详细设置参照官方文档进行:
开始实行
导入依赖
- 返回JSON数据,所以数据格式转换是必须的,需要
jaskson
; - 和风天气 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注入需要扫描的包
- 我将所有的外置工具都放到
utility
中; - 配置
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"; // 北京
}
编写实体类
- 返回显示的数据,
应答代码
,所需数据
;
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
}
编写逻辑层代码
- 调用和风天气API
- 处理数据,符合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);
}
}
控制层代码
- 返回视图解析器,返回页面显示详情信息
- 返回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层视图显示
- 动态加载的 使用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("天气:获取失败");
}
})
- 视图跳转,显示视图的内容

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判断所在地,自动使用所在地地点显示天气
- 工具调查,需要使用获取地址的api工具
- 需要用到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 改动原来的就可以,毕竟是为了保障以后
逻辑层
- 所需数据少,需要变动点少,索性放在一个里面,引入lombok,省下写getter/setter;
- 城市 是需要请求天气所需要的东西,如果进行变动的话需要考虑进去
- 请求响应失败的场合 需要返回个默认地址
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
}
}
- taobao的废止了,使用了ip9的接口去用;
IpParseService
返回一个城市name
,这个name
,CityCodeService
需要使用,我们使用这个去 查找城市代码; 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));
}
}
工具类
- 解析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
中加个字段? 再新写个实体类? 把IpParseService
的 IpData
提取出来?思路各色各样
人世间万事万物都是变得,唯唯物辩证法永恒。
