优化
This commit is contained in:
1 parent
d9a50e23dd
commit
4f1acaa451
25 files changed
+1265
-335
No files matched your search
@@ -0,0 +1,146 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// BackendWeatherController 天气实时数据代理。
|
||||
//
|
||||
// 数据源:中国天气网 d1.weather.com.cn(国内真实数据)。
|
||||
// 该接口有防盗链,浏览器直连会被 Referer 校验拦掉、也会跨域,
|
||||
// 因此由服务端带 Referer 抓取后统一返回 JSON。
|
||||
type BackendWeatherController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// 常用城市编码(中国天气网 citycode),未命中时按入参 code 直取
|
||||
var weatherCityCodes = map[string]string{
|
||||
"连云港": "101191001",
|
||||
"南京": "101190101",
|
||||
"苏州": "101190401",
|
||||
"无锡": "101190201",
|
||||
"常州": "101191101",
|
||||
"南通": "101190501",
|
||||
"扬州": "101190601",
|
||||
"徐州": "101190801",
|
||||
"盐城": "101190701",
|
||||
"淮安": "101190901",
|
||||
"镇江": "101190301",
|
||||
"泰州": "101191201",
|
||||
"宿迁": "101191301",
|
||||
"北京": "101010100",
|
||||
"上海": "101020100",
|
||||
"广州": "101280101",
|
||||
"深圳": "101280601",
|
||||
"杭州": "101210101",
|
||||
}
|
||||
|
||||
const defaultWeatherCity = "连云港"
|
||||
|
||||
// Now GET /backend/weather/now?city=连云港 或 ?code=101191201
|
||||
func (c *BackendWeatherController) Now() {
|
||||
city := strings.TrimSpace(c.GetString("city"))
|
||||
code := strings.TrimSpace(c.GetString("code"))
|
||||
if code == "" {
|
||||
code = weatherCityCodes[city]
|
||||
}
|
||||
if code == "" {
|
||||
code = weatherCityCodes[defaultWeatherCity]
|
||||
city = defaultWeatherCity
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 8 * time.Second}
|
||||
ts := strconv.FormatInt(time.Now().UnixMilli(), 10)
|
||||
referer := "https://www.weather.com.cn/weather1d/" + code + ".shtml"
|
||||
|
||||
// 主数据源:dingzhi(含天气现象 + 气温)。中国天气网已拒绝 http 明文连接,必须走 https。
|
||||
info := weatherFetchJSON(client, "https://d1.weather.com.cn/dingzhi/"+code+".html?_="+ts, referer)
|
||||
if wi, ok := info["weatherinfo"].(map[string]interface{}); ok {
|
||||
info = wi
|
||||
}
|
||||
// 兜底:sk_2d(实况气温,无天气现象)
|
||||
if len(info) == 0 {
|
||||
info = weatherFetchJSON(client, "https://d1.weather.com.cn/sk_2d/"+code+".html?_="+ts, referer)
|
||||
}
|
||||
|
||||
if len(info) == 0 {
|
||||
c.Ctx.Output.SetStatus(http.StatusBadGateway)
|
||||
c.Data["json"] = map[string]interface{}{"code": 502, "msg": "天气数据获取失败,请稍后重试"}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
cityName := weatherStr(info, "cityname", "city", "name")
|
||||
if cityName == "" {
|
||||
cityName = city
|
||||
}
|
||||
temp := strings.TrimSuffix(weatherStr(info, "temp", "real"), "℃")
|
||||
text := weatherStr(info, "weather", "weather1")
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"data": map[string]interface{}{
|
||||
"city": cityName,
|
||||
"temp": temp,
|
||||
"text": text,
|
||||
"update": weatherStr(info, "time", "fctime", "date"),
|
||||
"code": code,
|
||||
},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// weatherFetchJSON 带 Referer/UA 抓取中国天气网页面并解析其中的 JSON 对象
|
||||
func weatherFetchJSON(client *http.Client, url, referer string) map[string]interface{} {
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36")
|
||||
req.Header.Set("Referer", referer)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 返回体形如:var cityDZ101191001 ={"weatherinfo":{...}};var alarmDZ101191001 ={"w":[]}
|
||||
// 从第一个 '{' 起用 json.Decoder 只解出第一个完整对象(正则贪婪匹配会跨越多个对象导致解析失败)
|
||||
idx := bytes.IndexByte(body, '{')
|
||||
if idx < 0 {
|
||||
return nil
|
||||
}
|
||||
var out map[string]interface{}
|
||||
if json.NewDecoder(bytes.NewReader(body[idx:])).Decode(&out) != nil {
|
||||
return nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// weatherStr 按候选字段顺序取第一个非空字符串值
|
||||
func weatherStr(m map[string]interface{}, keys ...string) string {
|
||||
for _, k := range keys {
|
||||
if v, ok := m[k]; ok {
|
||||
if s, ok := v.(string); ok && strings.TrimSpace(s) != "" {
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in new issue
Block a user