【項目實踐】接入天氣api在用戶登錄后可以看到當前天氣
前期調研
經多方查找,發現和風天氣對于個人開發人相對有好,有詳細的api接口文檔以及實現案例。
優點:
- 只需要提供城市、授權ID和Key,就會進行返回 跟天氣相關的多樣態數據;
- 響應數據種類多,提供各類svg圖標樣式,后續擴展性可以延伸;

詳情查看官網
準備工作
注冊和風天氣賬號
- 注冊時需要準備郵箱,手機賬號
- 驗證碼注意檢查下垃圾郵件,有可能被默認規則攔截

- 添加項目,這里就開始注冊需要使用 的api了

- 注冊項目

- 創建完成后會顯示這個,下面的憑據 點擊;
![]()
- api Key 就是我們需要用的APIKey
![]()
- 詳細設置參照官方文檔進行:
開始實行
導入依賴
- 返回JSON數據,所以數據格式轉換是必須的,需要
jaskson; - 和風天氣 API響應的數據使用
Gzip壓縮,正常顯示失敗,需要設置HttpComponentsClientHttpRequestFactory,為了解決API響應壓縮導致的解析問題,確保能夠正確處理和風天氣 API 返回的可能經過 gzip 壓縮的 JSON 數據。
<!-- 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請求)
<!-- 掃描Controller和Service所在的包 -->
<context:component-scan base-package="com.utility"/>
<!-- 配置RestTemplate(用于發送HTTP請求) -->
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate"/>
目錄如下:

配置秘鑰
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"; // 北京
}
編寫實體類
- 返回顯示的數據,
應答代碼,所需數據;
// 頂層響應
public class WeatherResponse {
private String code;
private Now now;
// getter + setter
}
// 當前天氣詳情
public class Now {
private String temp;
private String text;
private String windDir;
private String humidity;
// getter + setter
}
編寫邏輯層代碼
- 調用和風天氣API
- 處理數據,符合URL規范,參考官方文檔進行
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,動態加載頁面,在頁面小地方加載內容
@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
$.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("天氣:獲取失敗");
}
})
- 視圖跳轉,顯示視圖的內容

<%@ 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;
- 城市 是需要請求天氣所需要的東西,如果進行變動的話需要考慮進去
- 請求響應失敗的場合 需要返回個默認地址
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工具 內存不足;
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地址
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 改動原來的就可以,畢竟是為了保障以后
邏輯層
原來功能:
public WeatherResponse getCurrentWeather(HttpServletRequest request) {
String cityId =
cityCodeService
.getCityIdByCityName(ipParseService
.getCityByIp(IpUtils.getRealIp(request)));
}
分析可拆分點,IP轉化日期單獨拿出,后面再單獨去調用;
public WeatherResponse getCurrentWeather(String city) {
String cityId = cityCodeService.getCityIdByCityName(city);
}
實體類
需求發生變化,頁面內容就要變更,取值的方式就多種多樣了,改之前的Entity,返回的ResponseVo中加個字段? 再新寫個實體類? 把IpParseService 的 IpData提取出來?思路各色各樣
人世間萬事萬物都是變得,唯唯物辯證法永恒。




浙公網安備 33010602011771號