1. 天气查询功能介绍
天气查询API在现代Web和移动应用中扮演着重要角色,它可以为用户提供实时天气数据、未来天气预报以及生活指数等关键信息。本文将详细介绍如何实现一个高效、稳定的天气查询服务,涵盖从API选择、异步编程实现到缓存优化的完整开发流程。
2. 天气API服务选择指南
在开发天气查询应用时,选择合适的API服务提供商至关重要。以下是几个主流的天气API服务对比:
- 和风天气API:国内数据覆盖全面,API文档完善,适合后端开发使用
- OpenWeatherMap:全球天气数据服务,国际化支持好
- 高德地图天气API:国内覆盖较全面,适合本地化应用
- 百度地图天气API:与百度地图集成良好,适合地图应用场景
本文以和风天气API为例,详细讲解调用天气API的完整实现方案,包括API密钥获取、请求构建、响应解析和错误处理等关键环节。
3. 天气查询API代码实现
3.1 获取和风天气API密钥
在开始天气查询开发前,首先需要在和风天气开发者平台注册账号并获取API密钥。注册流程简单,完成后即可获得免费额度的API访问权限。
3.2 天气查询类实现
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
public class WeatherAPI
{
private readonly string _apiKey;
private readonly string _baseUrl;
private readonly HttpClient _httpClient;
public WeatherAPI(string apiKey)
{
_apiKey = apiKey;
_baseUrl = "https://devapi.qweather.com/v7";
_httpClient = new HttpClient();
_httpClient.Timeout = TimeSpan.FromSeconds(10);
}
/// <summary>
/// 实现获取指定位置的实时天气数据
/// 支持城市ID或经纬度查询
/// </summary>
public async Task<Dictionary<string, object>> GetCurrentWeatherAsync(string location)
{
string url = $"{_baseUrl}/weather/now";
var parameters = new Dictionary<string, string>
{
{ "key", _apiKey },
{ "location", location } // 可以是城市ID或经纬度
};
try
{
using (var response = await _httpClient.GetAsync($"{url}?{BuildQueryString(parameters)}"))
{
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
dynamic data = JsonConvert.DeserializeObject(responseBody);
if (data.code == "200")
{
return data.now.ToObject<Dictionary<string, object>>();
}
else
{
Console.WriteLine($"查询失败: {data.code} - {data.msg}");
return null;
}
}
}
catch (Exception ex)
{
Console.WriteLine($"请求异常: {ex.Message}");
return null;
}
}
/// <summary>
///实现获取指定位置的天气预报数据
/// 支持3天或7天预报查询
/// </summary>
public async Task<List<Dictionary<string, object>>> GetWeatherForecastAsync(string location, int days = 3)
{
string url = days <= 3 ? $"{_baseUrl}/weather/3d" : $"{_baseUrl}/weather/7d";
var parameters = new Dictionary<string, string>
{
{ "key", _apiKey },
{ "location", location }
};
try
{
using (var response = await _httpClient.GetAsync($"{url}?{BuildQueryString(parameters)}"))
{
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
dynamic data = JsonConvert.DeserializeObject(responseBody);
if (data.code == "200")
{
var dailyList = data.daily.ToObject<List<Dictionary<string, object>>>();
return dailyList.GetRange(0, Math.Min(days, dailyList.Count));
}
else
{
Console.WriteLine($"查询失败: {data.code} - {data.msg}");
return new List<Dictionary<string, object>>();
}
}
}
catch (Exception ex)
{
Console.WriteLine($"请求异常: {ex.Message}");
return new List<Dictionary<string, object>>();
}
}
/// <summary>
///实现获取指定位置的生活指数数据
/// 包括穿衣、洗车、紫外线、运动、旅游指数
/// </summary>
public async Task<List<Dictionary<string, object>>> GetLifeIndexAsync(string location)
{
string url = $"{_baseUrl}/indices/1d";
var parameters = new Dictionary<string, string>
{
{ "key", _apiKey },
{ "location", location },
{ "type", "1,2,3,5,9" } // 包括穿衣、洗车、紫外线、运动、旅游指数
};
try
{
using (var response = await _httpClient.GetAsync($"{url}?{BuildQueryString(parameters)}"))
{
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
dynamic data = JsonConvert.DeserializeObject(responseBody);
if (data.code == "200")
{
return data.daily.ToObject<List<Dictionary<string, object>>>();
}
else
{
Console.WriteLine($"查询失败: {data.code} - {data.msg}");
return new List<Dictionary<string, object>>();
}
}
}
catch (Exception ex)
{
Console.WriteLine($"请求异常: {ex.Message}");
return new List<Dictionary<string, object>>();
}
}
/// <summary>
/// 构建查询字符串
/// </summary>
private string BuildQueryString(Dictionary<string, string> parameters)
{
var queryParts = new List<string>();
foreach (var param in parameters)
{
queryParts.Add($"{param.Key}={System.Net.WebUtility.UrlEncode(param.Value)}");
}
return string.Join("&", queryParts);
}
}
3.3天气查询实例代码示例
// 使用示例
class Program
{
static async Task Main(string[] args)
{
// 替换为您的API密钥
string apiKey = "your_api_key_here";
var weatherAPI = new WeatherAPI(apiKey);
// 查询北京天气(使用城市ID)
string location = "101010100"; // 北京的城市ID
// 获取实时天气
var currentWeather = await weatherAPI.GetCurrentWeatherAsync(location);
if (currentWeather != null)
{
Console.WriteLine("实时天气:");
Console.WriteLine($"温度: {currentWeather["temp"]}°C");
Console.WriteLine($"天气: {currentWeather["text"]}");
Console.WriteLine($"风向: {currentWeather["windDir"]} {currentWeather["windScale"]}级");
Console.WriteLine($"湿度: {currentWeather["humidity"]}%");
}
// 获取3天预报
var forecast = await weatherAPI.GetWeatherForecastAsync(location);
Console.WriteLine("\n未来3天预报:");
foreach (var day in forecast)
{
Console.WriteLine($"日期: {day["fxDate"]}");
Console.WriteLine($"天气: {day["textDay"]} -> {day["textNight"]}");
Console.WriteLine($"温度: {day["tempMin"]}°C - {day["tempMax"]}°C");
Console.WriteLine();
}
// 获取生活指数
var lifeIndices = await weatherAPI.GetLifeIndexAsync(location);
Console.WriteLine("\n生活指数:");
foreach (var index in lifeIndices)
{
Console.WriteLine($"{index["name"]}: {index["level"]} - {index["text"]}");
}
}
}
4. ASP.NET Core天气查询Web API服务实现
以下是使用ASP.NET Core框架构建高性能天气查询Web API服务的完整实现,支持跨平台部署和高并发访问:
// Program.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = WebApplication.CreateBuilder(args);
// 添加服务
builder.Services.AddControllers();
builder.Services.AddSingleton<WeatherAPI>(new WeatherAPI("your_api_key_here"));
var app = builder.Build();
// 配置中间件
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
// WeatherController.cs
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;
[ApiController]
[Route("api/[controller]")]
public class WeatherController : ControllerBase
{
private readonly WeatherAPI _weatherApi;
public WeatherController(WeatherAPI weatherApi)
{
_weatherApi = weatherApi;
}
[HttpGet("current")]
public async Task<IActionResult> GetCurrentWeather([FromQuery] string location)
{
if (string.IsNullOrEmpty(location))
{
return BadRequest(new { error = "缺少location参数" });
}
var weatherData = await _weatherApi.GetCurrentWeatherAsync(location);
if (weatherData != null)
{
return Ok(weatherData);
}
else
{
return StatusCode(500, new { error = "查询失败" });
}
}
[HttpGet("forecast")]
public async Task<IActionResult> GetForecast([FromQuery] string location, [FromQuery] int days = 3)
{
if (string.IsNullOrEmpty(location))
{
return BadRequest(new { error = "缺少location参数" });
}
var forecastData = await _weatherApi.GetWeatherForecastAsync(location, days);
return Ok(forecastData);
}
[HttpGet("life")]
public async Task<IActionResult> GetLifeIndex([FromQuery] string location)
{
if (string.IsNullOrEmpty(location))
{
return BadRequest(new { error = "缺少location参数" });
}
var lifeData = await _weatherApi.GetLifeIndexAsync(location);
return Ok(lifeData);
}
}
5. 天气查询服务错误处理与性能优化
5.1 健壮的错误处理机制
在生产环境中,完善的错误处理机制对保证天气查询API服务的稳定性至关重要:
/// <summary>
/// 获取指定位置的实时天气,带完善的错误处理
/// </summary>
public async Task<Dictionary<string, object>> GetCurrentWeatherAsync(string location)
{
if (string.IsNullOrEmpty(location))
{
Console.WriteLine("错误: 位置参数不能为空");
return null;
}
string url = $"{_baseUrl}/weather/now";
var parameters = new Dictionary<string, string>
{
{ "key", _apiKey },
{ "location", location }
};
try
{
using (var response = await _httpClient.GetAsync($"{url}?{BuildQueryString(parameters)}"))
{
// 检查HTTP状态码
if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"HTTP错误: {response.StatusCode} - {response.ReasonPhrase}");
return null;
}
string responseBody = await response.Content.ReadAsStringAsync();
// 检查响应内容是否为空
if (string.IsNullOrEmpty(responseBody))
{
Console.WriteLine("错误: API返回空响应");
return null;
}
// 解析JSON
dynamic data;
try
{
data = JsonConvert.DeserializeObject(responseBody);
}
catch (JsonException ex)
{
Console.WriteLine($"错误: 无法解析API返回的数据 - {ex.Message}");
return null;
}
// 检查API返回的状态码
if (data.code == "200")
{
return data.now.ToObject<Dictionary<string, object>>();
}
else
{
string errorMsg = data.msg != null ? $"查询失败: {data.code} - {data.msg}" : $"查询失败: {data.code}";
Console.WriteLine(errorMsg);
return null;
}
}
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
Console.WriteLine("错误: 请求超时,请检查网络连接");
return null;
}
catch (HttpRequestException ex)
{
Console.WriteLine($"网络请求异常: {ex.Message}");
return null;
}
catch (Exception ex)
{
Console.WriteLine($"发生未知错误: {ex.Message}");
return null;
}
}
5.2 高性能缓存优化实现
为优化天气查询API性能和减少第三方API调用次数,实现高效的缓存机制:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
public class WeatherAPI
{
private readonly string _apiKey;
private readonly string _baseUrl;
private readonly HttpClient _httpClient;
private readonly ConcurrentDictionary<string, CacheItem> _cache;
private readonly TimeSpan _cacheTtl;
private class CacheItem
{
public object Data { get; set; }
public DateTime Timestamp { get; set; }
}
public WeatherAPI(string apiKey, int cacheTtlSeconds = 300) // 缓存默认5分钟
{
_apiKey = apiKey;
_baseUrl = "https://devapi.qweather.com/v7";
_httpClient = new HttpClient();
_httpClient.Timeout = TimeSpan.FromSeconds(10);
_cache = new ConcurrentDictionary<string, CacheItem>();
_cacheTtl = TimeSpan.FromSeconds(cacheTtlSeconds);
}
private string GetCacheKey(string functionName, params string[] parameters)
{
return $"{functionName}:{string.Join(":", parameters)}";
}
private bool IsCacheValid(CacheItem cacheItem)
{
return cacheItem != null && (DateTime.UtcNow - cacheItem.Timestamp) < _cacheTtl;
}
public async Task<Dictionary<string, object>> GetCurrentWeatherAsync(string location)
{
string cacheKey = GetCacheKey("current", location);
// 检查缓存
if (_cache.TryGetValue(cacheKey, out var cachedItem) && IsCacheValid(cachedItem))
{
Console.WriteLine("使用缓存数据");
return (Dictionary<string, object>)cachedItem.Data;
}
// 原有API调用逻辑...
var data = await FetchCurrentWeatherFromApi(location);
// 保存到缓存
if (data != null)
{
_cache[cacheKey] = new CacheItem { Data = data, Timestamp = DateTime.UtcNow };
}
return data;
}
private async Task<Dictionary<string, object>> FetchCurrentWeatherFromApi(string location)
{
// 这里是实际的API调用代码,与之前的实现相同
// ...
}
// 其他方法类似实现...
}
6. 天气查询API应用场景与实践
6.1 开发个人网站天气组件
以下是使用天气查询API开发的Web服务配合前端实现的天气显示组件:
<div class="weather-widget">
<h3>今日天气</h3>
<div class="weather-info">
<div id="weather-temp">加载中...</div>
<div id="weather-text">--</div>
<div id="weather-detail">--</div>
</div>
</div>
<script>
fetch('/api/weather/current?location=101010100')
.then(response => response.json())
.then(data => {
document.getElementById('weather-temp').textContent = `${data.temp}°C`;
document.getElementById('weather-text').textContent = data.text;
document.getElementById('weather-detail').textContent = `${data.windDir} ${data.windScale}级 · 湿度 ${data.humidity}%`;
})
.catch(error => {
console.error('获取天气数据失败:', error);
});
</script>
6.2 实现天气预报邮件推送服务
使用定时任务实现天气预报邮件自动推送功能:
using System;
using System.Collections.Generic;
using System.Net.Mail;
using System.Threading.Tasks;
public class WeatherEmailService
{
private readonly SmtpClient _smtpClient;
private readonly string _fromEmail;
public WeatherEmailService(string smtpServer, int port, bool useSsl, string username, string password, string fromEmail)
{
_smtpClient = new SmtpClient(smtpServer, port)
{
EnableSsl = useSsl,
Credentials = new System.Net.NetworkCredential(username, password)
};
_fromEmail = fromEmail;
}
public async Task SendWeatherEmailAsync(Dictionary<string, object> weatherData, string recipientEmail)
{
if (weatherData == null || string.IsNullOrEmpty(recipientEmail))
return;
// 构建邮件内容
string today = DateTime.Now.ToString("yyyy-MM-dd");
string subject = $"{today} 天气预报";
string content = $@"<h2>{today} 天气预报</h2>
<p>实时温度: {weatherData["temp"]}°C</p>
<p>天气状况: {weatherData["text"]}</p>
<p>风向风力: {weatherData["windDir"]} {weatherData["windScale"]}级</p>
<p>湿度: {weatherData["humidity"]}%</p>";
using (var message = new MailMessage(_fromEmail, recipientEmail)
{
Subject = subject,
Body = content,
IsBodyHtml = true
})
{
await _smtpClient.SendMailAsync(message);
}
}
}
// 使用示例
public class Program
{
static async Task Main(string[] args)
{
// 初始化天气API
var weatherApi = new WeatherAPI("your_api_key_here");
// 获取天气数据
var weatherData = await weatherApi.GetCurrentWeatherAsync("101010100");
if (weatherData != null)
{
// 初始化邮件服务
var emailService = new WeatherEmailService(
smtpServer: "smtp.example.com",
port: 587,
useSsl: true,
username: "your_email@example.com",
password: "your_password",
fromEmail: "your_email@example.com"
);
// 发送邮件
await emailService.SendWeatherEmailAsync(weatherData, "recipient@example.com");
}
}
}
7. 天气查询API开发总结与最佳实践
通过本文的天气查询API开发,我们涉及了:
- 如何选择适合天气查询API服务提供商
- 使用HttpClient和异步编程实现高效的API调用
- 基于ASP.NET Core框架构建RESTful天气查询Web API
- 实现健壮的错误处理和高性能缓存优化机制
- 多个实用的天气查询API应用场景示例
这些天气查询实现方案适用于各类.NET应用,包括Web应用、桌面软件和移动应用后端,能为用户提供实时、准确的天气信息服务。
8. 天气查询开发注意事项
- 使用天气API时请注意查看服务商的使用条款和免费额度限制
- 确保在生产环境中妥善保管API密钥,建议使用ASP.NET Core的配置管理或密钥保管库
- 在ASP.NET Core应用中,HttpClient应通过IHttpClientFactory进行管理,避免连接池耗尽
- 高流量应用应考虑使用Redis等分布式缓存优化性能
- 根据实际需求调整数据刷新频率,平衡实时性和API调用成本
- 生产环境中使用Serilog或NLog等专业日志框架替代Console.WriteLine