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开发,我们涉及了:

  1. 如何选择适合天气查询API服务提供商
  2. 使用HttpClient和异步编程实现高效的API调用
  3. 基于ASP.NET Core框架构建RESTful天气查询Web API
  4. 实现健壮的错误处理和高性能缓存优化机制
  5. 多个实用的天气查询API应用场景示例

这些天气查询实现方案适用于各类.NET应用,包括Web应用、桌面软件和移动应用后端,能为用户提供实时、准确的天气信息服务。

8. 天气查询开发注意事项

  • 使用天气API时请注意查看服务商的使用条款和免费额度限制
  • 确保在生产环境中妥善保管API密钥,建议使用ASP.NET Core的配置管理或密钥保管库
  • 在ASP.NET Core应用中,HttpClient应通过IHttpClientFactory进行管理,避免连接池耗尽
  • 高流量应用应考虑使用Redis等分布式缓存优化性能
  • 根据实际需求调整数据刷新频率,平衡实时性和API调用成本
  • 生产环境中使用Serilog或NLog等专业日志框架替代Console.WriteLine