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) // } // } }