Files
silk/server-go/internal/service/weather.go
T

134 lines
3.4 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 service
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"time"
)
const qweatherAPIBase = "https://devapi.qweather.com"
// WeatherNow 实时天气
type WeatherNow struct {
Temp float64 `json:"temp"`
Humidity float64 `json:"humidity"`
Text string `json:"text"`
}
// DailyForecast 逐日预报
type DailyForecast struct {
Date string `json:"date"`
TextDay string `json:"textDay"`
TextNight string `json:"textNight"`
TempMax float64 `json:"tempMax"`
TempMin float64 `json:"tempMin"`
Humidity float64 `json:"humidity"`
}
// WeatherService 和风天气客户端(未配置时 Configured()=false,调用返回明确错误)
type WeatherService struct {
key string
location string
baseURL string
client *http.Client
}
// NewWeatherService 创建和风天气客户端
func NewWeatherService(key, location string) *WeatherService {
return &WeatherService{
key: key,
location: location,
baseURL: qweatherAPIBase,
client: &http.Client{Timeout: 15 * time.Second},
}
}
// Configured 是否已配置 API Key 与位置
func (s *WeatherService) Configured() bool {
return s.key != "" && s.location != ""
}
// FetchNow 实时天气
func (s *WeatherService) FetchNow(ctx context.Context) (*WeatherNow, error) {
var out struct {
Code string `json:"code"`
Now struct {
Temp string `json:"temp"`
Text string `json:"text"`
Humidity string `json:"humidity"`
} `json:"now"`
}
if err := s.get(ctx, "/v7/weather/now", &out); err != nil {
return nil, err
}
temp, _ := strconv.ParseFloat(out.Now.Temp, 64)
hum, _ := strconv.ParseFloat(out.Now.Humidity, 64)
return &WeatherNow{Temp: temp, Humidity: hum, Text: out.Now.Text}, nil
}
// FetchDaily 未来 3 天预报
func (s *WeatherService) FetchDaily(ctx context.Context) ([]DailyForecast, error) {
var out struct {
Code string `json:"code"`
Daily []struct {
FxDate string `json:"fxDate"`
TextDay string `json:"textDay"`
TextNight string `json:"textNight"`
TempMax string `json:"tempMax"`
TempMin string `json:"tempMin"`
Humidity string `json:"humidity"`
} `json:"daily"`
}
if err := s.get(ctx, "/v7/weather/3d", &out); err != nil {
return nil, err
}
list := make([]DailyForecast, 0, len(out.Daily))
for _, d := range out.Daily {
max, _ := strconv.ParseFloat(d.TempMax, 64)
min, _ := strconv.ParseFloat(d.TempMin, 64)
hum, _ := strconv.ParseFloat(d.Humidity, 64)
list = append(list, DailyForecast{
Date: d.FxDate, TextDay: d.TextDay, TextNight: d.TextNight,
TempMax: max, TempMin: min, Humidity: hum,
})
}
return list, nil
}
// get 发起 GET 并校验 code=200
func (s *WeatherService) get(ctx context.Context, path string, out any) error {
if !s.Configured() {
return fmt.Errorf("天气服务未配置(QWEATHER_API_KEY/QWEATHER_LOCATION")
}
u := s.baseURL + path + "?location=" + url.QueryEscape(s.location) + "&key=" + url.QueryEscape(s.key)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil {
return err
}
resp, err := s.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if err := json.Unmarshal(body, out); err != nil {
return err
}
var head struct {
Code string `json:"code"`
}
_ = json.Unmarshal(body, &head)
if head.Code != "200" {
return fmt.Errorf("和风天气返回错误 code=%s", head.Code)
}
return nil
}