Files
sundynix-plant-be/utils/location/weather.go
T
2026-02-06 14:44:06 +08:00

187 lines
6.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package location
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"time"
)
// ########################### 1. 结构体定义(对齐高德天气API返回格式) ###########################
// WeatherResponse 高德天气API顶层响应结构体
type WeatherResponse struct {
Status string `json:"status"` // 1=成功,0=失败
Info string `json:"info"` // 错误信息
Infocode string `json:"infocode"` // 错误码
Lives []LiveWeather `json:"lives"` // 实时天气(数组,仅1条数据)
Forecasts []Forecast `json:"forecasts"` // 天气预报(数组,仅1条数据)
}
// LiveWeather 实时天气结构体
type LiveWeather struct {
Province string `json:"province"` // 省份
City string `json:"city"` // 城市
Adcode string `json:"adcode"` // 行政区划编码
Weather string `json:"weather"` // 天气现象(如晴、阴)
Temperature string `json:"temperature"` // 实时气温(℃)
WindDirection string `json:"winddirection"` // 风向(如东、西南)
WindPower string `json:"windpower"` // 风力(如3级)
Humidity string `json:"humidity"` // 湿度(%
ReportTime string `json:"reporttime"` // 数据更新时间
}
// Forecast 天气预报顶层结构体(包含多日预报)
type Forecast struct {
Province string `json:"province"` // 省份
City string `json:"city"` // 城市
Adcode string `json:"adcode"` // 行政区划编码
ReportTime string `json:"reporttime"` // 预报发布时间
Casts []ForecastDay `json:"casts"` // 每日预报(未来3天)
}
// ForecastDay 单日天气预报
type ForecastDay struct {
Date string `json:"date"` // 日期(yyyy-MM-dd
Week string `json:"week"` // 星期(1=周一,7=周日)
DayWeather string `json:"dayweather"` // 白天天气
NightWeather string `json:"nightweather"` // 夜间天气
DayTemp string `json:"daytemp"` // 白天温度
NightTemp string `json:"nighttemp"` // 夜间温度
DayWindDir string `json:"daywinddir"` // 白天风向
NightWindDir string `json:"nightwinddir"` // 夜间风向
DayWindPower string `json:"daywindpower"` // 白天风力
NightWindPower string `json:"nightwindpower"` // 夜间风力
}
// ########################### 2. 配置常量 ###########################
const (
amapWeatherApi = "https://restapi.amap.com/v3/weather/weatherInfo"
)
// GetWeather 根据行政区划编码查询天气
// adcode: 行政区划编码(如110101=北京市东城区)
// extensions: 查询类型(base=实时,all=实时+预报)
func GetWeather(adcode string, extensions string) (*WeatherResponse, error) {
// 1. 参数校验
if adcode == "" {
return nil, errors.New("行政区划编码adcode不能为空")
}
if extensions == "" {
extensions = "base" // 默认查实时天气
}
if extensions != "base" && extensions != "all" {
return nil, errors.New("extensions仅支持base(实时)或all(实时+预报)")
}
// 2. 构建请求URL和参数
baseURL, err := url.Parse(amapWeatherApi)
if err != nil {
log.Printf("解析基础URL失败: %v", err)
return nil, err
}
// 设置查询参数
params := url.Values{}
params.Set("key", appKey)
params.Set("city", adcode) // 核心参数:行政区划编码
params.Set("extensions", extensions)
params.Set("output", "json") // 固定返回JSON格式
baseURL.RawQuery = params.Encode()
// 3. 发送HTTP请求(全局客户端,复用连接)
client := &http.Client{
Timeout: 10 * time.Second, // 10秒超时
}
req, err := http.NewRequest(http.MethodGet, baseURL.String(), nil)
if err != nil {
log.Printf("构建请求失败: %v", err)
return nil, err
}
resp, err := client.Do(req)
if err != nil {
log.Printf("发送请求失败: %v", err)
return nil, err
}
defer resp.Body.Close() // 强制关闭响应体
// 4. 检查HTTP状态码
if resp.StatusCode != http.StatusOK {
errMsg := fmt.Sprintf("请求失败,状态码: %d", resp.StatusCode)
log.Println(errMsg)
return nil, errors.New(errMsg)
}
// 5. 读取响应体
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("读取响应体失败: %v", err)
return nil, err
}
// 6. 解析JSON响应
var weatherResp WeatherResponse
if err := json.Unmarshal(bodyBytes, &weatherResp); err != nil {
log.Printf("解析JSON失败: %v, 响应内容: %s", err, string(bodyBytes))
return nil, err
}
// 7. 检查API业务状态
if weatherResp.Status != "1" {
errMsg := fmt.Sprintf("高德天气API返回失败: status=%s, info=%s, infocode=%s",
weatherResp.Status, weatherResp.Info, weatherResp.Infocode)
log.Println(errMsg)
return nil, errors.New(errMsg)
}
// 8. 校验返回数据非空
if extensions == "base" && len(weatherResp.Lives) == 0 {
return nil, errors.New("未查询到实时天气数据")
}
if extensions == "all" && (len(weatherResp.Lives) == 0 || len(weatherResp.Forecasts) == 0) {
return nil, errors.New("未查询到天气数据(实时/预报)")
}
return &weatherResp, nil
}
// ########################### 4. 测试示例 ###########################
func main() {
// 示例1:查询北京市东城区(adcode=110101)实时天气
adcode := "110101"
// 可选:extensions="all" 查询实时+未来3天预报
weatherResp, err := GetWeather(adcode, "base")
if err != nil {
log.Fatalf("查询天气失败: %v", err)
}
// 打印实时天气
fmt.Println("=== 实时天气 ===")
live := weatherResp.Lives[0] // 实时天气数组仅1条数据
fmt.Printf("省份:%s\n", live.Province)
fmt.Printf("城市:%s\n", live.City)
fmt.Printf("行政区划编码:%s\n", live.Adcode)
fmt.Printf("天气:%s\n", live.Weather)
fmt.Printf("实时气温:%s℃\n", live.Temperature)
fmt.Printf("风向:%s\n", live.WindDirection)
fmt.Printf("风力:%s级\n", live.WindPower)
fmt.Printf("湿度:%s%%\n", live.Humidity)
fmt.Printf("数据更新时间:%s\n", live.ReportTime)
// 若查询的是all(实时+预报),打印预报数据
// if len(weatherResp.Forecasts) > 0 {
// fmt.Println("\n=== 未来3天预报 ===")
// forecast := weatherResp.Forecasts[0]
// for _, day := range forecast.Casts {
// fmt.Printf("\n日期:%s(星期%s\n", day.Date, day.Week)
// fmt.Printf("白天天气:%s,温度:%s℃\n", day.DayWeather, day.DayTemp)
// fmt.Printf("夜间天气:%s,温度:%s℃\n", day.NightWeather, day.NightTemp)
// fmt.Printf("白天风向/风力:%s/%s级\n", day.DayWindDir, day.DayWindPower)
// }
// }
}