- 城市列表由18个常用扩至386个地级市(和风LocationList地市级) - 附 gen-cities.ps1 从源数据一键再生成, 支持双源下载 - 前端城市选择器由后端 citiesByProv 驱动, 无需改动
730 lines
27 KiB
Go
730 lines
27 KiB
Go
package main
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"log"
|
||
"net/http"
|
||
"regexp"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
const qweatherHost = "https://pb4nmv4qnu.re.qweatherapi.com"
|
||
|
||
type City struct {
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
Adm2 string `json:"adm2"`
|
||
Adm1 string `json:"adm1"`
|
||
}
|
||
|
||
var cities = []City{
|
||
{"101010100", "北京", "北京", "北京"},
|
||
{"101030100", "天津", "天津", "天津"},
|
||
{"101020100", "上海", "上海", "上海"},
|
||
{"101040100", "重庆", "重庆", "重庆"},
|
||
{"101090101", "石家庄", "石家庄", "河北"},
|
||
{"101090201", "保定", "保定", "河北"},
|
||
{"101090301", "张家口", "张家口", "河北"},
|
||
{"101090401", "承德", "承德", "河北"},
|
||
{"101090501", "唐山", "唐山", "河北"},
|
||
{"101090601", "廊坊", "廊坊", "河北"},
|
||
{"101090701", "沧州", "沧州", "河北"},
|
||
{"101090801", "衡水", "衡水", "河北"},
|
||
{"101090901", "邢台", "邢台", "河北"},
|
||
{"101091001", "邯郸", "邯郸", "河北"},
|
||
{"101091101", "秦皇岛", "秦皇岛", "河北"},
|
||
{"101091201", "雄安新区", "雄安新区", "河北"},
|
||
{"101100101", "太原", "太原", "山西"},
|
||
{"101100201", "大同", "大同", "山西"},
|
||
{"101100301", "阳泉", "阳泉", "山西"},
|
||
{"101100401", "晋中", "晋中", "山西"},
|
||
{"101100501", "长治", "长治", "山西"},
|
||
{"101100601", "晋城", "晋城", "山西"},
|
||
{"101100701", "临汾", "临汾", "山西"},
|
||
{"101100801", "运城", "运城", "山西"},
|
||
{"101100901", "朔州", "朔州", "山西"},
|
||
{"101101001", "忻州", "忻州", "山西"},
|
||
{"101101100", "吕梁", "吕梁", "山西"},
|
||
{"101070101", "沈阳", "沈阳", "辽宁"},
|
||
{"101070201", "大连", "大连", "辽宁"},
|
||
{"101070301", "鞍山", "鞍山", "辽宁"},
|
||
{"101070401", "抚顺", "抚顺", "辽宁"},
|
||
{"101070501", "本溪", "本溪", "辽宁"},
|
||
{"101070601", "丹东", "丹东", "辽宁"},
|
||
{"101070701", "锦州", "锦州", "辽宁"},
|
||
{"101070801", "营口", "营口", "辽宁"},
|
||
{"101070901", "阜新", "阜新", "辽宁"},
|
||
{"101071001", "辽阳", "辽阳", "辽宁"},
|
||
{"101071101", "铁岭", "铁岭", "辽宁"},
|
||
{"101071201", "朝阳", "朝阳", "辽宁"},
|
||
{"101071301", "盘锦", "盘锦", "辽宁"},
|
||
{"101071401", "葫芦岛", "葫芦岛", "辽宁"},
|
||
{"101060101", "长春", "长春", "吉林"},
|
||
{"101060201", "吉林", "吉林", "吉林"},
|
||
{"101060301", "延边朝鲜族自治州", "延边朝鲜族自治州", "吉林"},
|
||
{"101060401", "四平", "四平", "吉林"},
|
||
{"101060501", "通化", "通化", "吉林"},
|
||
{"101060601", "白城", "白城", "吉林"},
|
||
{"101060701", "辽源", "辽源", "吉林"},
|
||
{"101060801", "松原", "松原", "吉林"},
|
||
{"101060901", "白山", "白山", "吉林"},
|
||
{"101050101", "哈尔滨", "哈尔滨", "黑龙江"},
|
||
{"101050201", "齐齐哈尔", "齐齐哈尔", "黑龙江"},
|
||
{"101050301", "牡丹江", "牡丹江", "黑龙江"},
|
||
{"101050401", "佳木斯", "佳木斯", "黑龙江"},
|
||
{"101050501", "绥化", "绥化", "黑龙江"},
|
||
{"101050601", "黑河", "黑河", "黑龙江"},
|
||
{"101050701", "大兴安岭地区", "大兴安岭地区", "黑龙江"},
|
||
{"101050801", "伊春", "伊春", "黑龙江"},
|
||
{"101050901", "大庆", "大庆", "黑龙江"},
|
||
{"101051001", "七台河", "七台河", "黑龙江"},
|
||
{"101051101", "鸡西", "鸡西", "黑龙江"},
|
||
{"101051201", "鹤岗", "鹤岗", "黑龙江"},
|
||
{"101051301", "双鸭山", "双鸭山", "黑龙江"},
|
||
{"101190101", "南京", "南京", "江苏"},
|
||
{"101190201", "无锡", "无锡", "江苏"},
|
||
{"101190301", "镇江", "镇江", "江苏"},
|
||
{"101190401", "苏州", "苏州", "江苏"},
|
||
{"101190501", "南通", "南通", "江苏"},
|
||
{"101190601", "扬州", "扬州", "江苏"},
|
||
{"101190701", "盐城", "盐城", "江苏"},
|
||
{"101190801", "徐州", "徐州", "江苏"},
|
||
{"101190901", "淮安", "淮安", "江苏"},
|
||
{"101191001", "连云港", "连云港", "江苏"},
|
||
{"101191101", "常州", "常州", "江苏"},
|
||
{"101191201", "泰州", "泰州", "江苏"},
|
||
{"101191301", "宿迁", "宿迁", "江苏"},
|
||
{"101210101", "杭州", "杭州", "浙江"},
|
||
{"101210201", "湖州", "湖州", "浙江"},
|
||
{"101210301", "嘉兴", "嘉兴", "浙江"},
|
||
{"101210401", "宁波", "宁波", "浙江"},
|
||
{"101210501", "绍兴", "绍兴", "浙江"},
|
||
{"101210601", "台州", "台州", "浙江"},
|
||
{"101210701", "温州", "温州", "浙江"},
|
||
{"101210801", "丽水", "丽水", "浙江"},
|
||
{"101210901", "金华", "金华", "浙江"},
|
||
{"101211001", "衢州", "衢州", "浙江"},
|
||
{"101211101", "舟山", "舟山", "浙江"},
|
||
{"101220101", "合肥", "合肥", "安徽"},
|
||
{"101220201", "蚌埠", "蚌埠", "安徽"},
|
||
{"101220301", "芜湖", "芜湖", "安徽"},
|
||
{"101220401", "淮南", "淮南", "安徽"},
|
||
{"101220501", "马鞍山", "马鞍山", "安徽"},
|
||
{"101220601", "安庆", "安庆", "安徽"},
|
||
{"101220701", "宿州", "宿州", "安徽"},
|
||
{"101220801", "阜阳", "阜阳", "安徽"},
|
||
{"101220901", "亳州", "亳州", "安徽"},
|
||
{"101221001", "黄山", "黄山", "安徽"},
|
||
{"101221101", "滁州", "滁州", "安徽"},
|
||
{"101221201", "淮北", "淮北", "安徽"},
|
||
{"101221301", "铜陵", "铜陵", "安徽"},
|
||
{"101221401", "宣城", "宣城", "安徽"},
|
||
{"101221501", "六安", "六安", "安徽"},
|
||
{"101221701", "池州", "池州", "安徽"},
|
||
{"101230101", "福州", "福州", "福建"},
|
||
{"101230201", "厦门", "厦门", "福建"},
|
||
{"101230301", "宁德", "宁德", "福建"},
|
||
{"101230401", "莆田", "莆田", "福建"},
|
||
{"101230501", "泉州", "泉州", "福建"},
|
||
{"101230601", "漳州", "漳州", "福建"},
|
||
{"101230701", "龙岩", "龙岩", "福建"},
|
||
{"101230801", "三明", "三明", "福建"},
|
||
{"101230901", "南平", "南平", "福建"},
|
||
{"101240101", "南昌", "南昌", "江西"},
|
||
{"101240201", "九江", "九江", "江西"},
|
||
{"101240301", "上饶", "上饶", "江西"},
|
||
{"101240401", "抚州", "抚州", "江西"},
|
||
{"101240501", "宜春", "宜春", "江西"},
|
||
{"101240601", "吉安", "吉安", "江西"},
|
||
{"101240701", "赣州", "赣州", "江西"},
|
||
{"101240801", "景德镇", "景德镇", "江西"},
|
||
{"101240901", "萍乡", "萍乡", "江西"},
|
||
{"101241001", "新余", "新余", "江西"},
|
||
{"101241101", "鹰潭", "鹰潭", "江西"},
|
||
{"101120101", "济南", "济南", "山东"},
|
||
{"101120201", "青岛", "青岛", "山东"},
|
||
{"101120301", "淄博", "淄博", "山东"},
|
||
{"101120401", "德州", "德州", "山东"},
|
||
{"101120501", "烟台", "烟台", "山东"},
|
||
{"101120601", "潍坊", "潍坊", "山东"},
|
||
{"101120701", "济宁", "济宁", "山东"},
|
||
{"101120801", "泰安", "泰安", "山东"},
|
||
{"101120901", "临沂", "临沂", "山东"},
|
||
{"101121001", "菏泽", "菏泽", "山东"},
|
||
{"101121101", "滨州", "滨州", "山东"},
|
||
{"101121201", "东营", "东营", "山东"},
|
||
{"101121301", "威海", "威海", "山东"},
|
||
{"101121401", "枣庄", "枣庄", "山东"},
|
||
{"101121501", "日照", "日照", "山东"},
|
||
{"101121701", "聊城", "聊城", "山东"},
|
||
{"101180101", "郑州", "郑州", "河南"},
|
||
{"101180201", "安阳", "安阳", "河南"},
|
||
{"101180301", "新乡", "新乡", "河南"},
|
||
{"101180401", "许昌", "许昌", "河南"},
|
||
{"101180501", "平顶山", "平顶山", "河南"},
|
||
{"101180601", "信阳", "信阳", "河南"},
|
||
{"101180701", "南阳", "南阳", "河南"},
|
||
{"101180801", "开封", "开封", "河南"},
|
||
{"101180901", "洛阳", "洛阳", "河南"},
|
||
{"101181001", "商丘", "商丘", "河南"},
|
||
{"101181101", "焦作", "焦作", "河南"},
|
||
{"101181201", "鹤壁", "鹤壁", "河南"},
|
||
{"101181301", "濮阳", "濮阳", "河南"},
|
||
{"101181401", "周口", "周口", "河南"},
|
||
{"101181501", "漯河", "漯河", "河南"},
|
||
{"101181601", "驻马店", "驻马店", "河南"},
|
||
{"101181701", "三门峡", "三门峡", "河南"},
|
||
{"101181801", "济源", "济源", "河南"},
|
||
{"101200101", "武汉", "武汉", "湖北"},
|
||
{"101200201", "襄阳", "襄阳", "湖北"},
|
||
{"101200301", "鄂州", "鄂州", "湖北"},
|
||
{"101200401", "孝感", "孝感", "湖北"},
|
||
{"101200501", "黄冈", "黄冈", "湖北"},
|
||
{"101200601", "黄石", "黄石", "湖北"},
|
||
{"101200701", "咸宁", "咸宁", "湖北"},
|
||
{"101200801", "荆州", "荆州", "湖北"},
|
||
{"101200901", "宜昌", "宜昌", "湖北"},
|
||
{"101201001", "恩施土家族苗族自治州", "恩施土家族苗族自治州", "湖北"},
|
||
{"101201101", "十堰", "十堰", "湖北"},
|
||
{"101201201", "神农架林区", "神农架林区", "湖北"},
|
||
{"101201301", "随州", "随州", "湖北"},
|
||
{"101201401", "荆门", "荆门", "湖北"},
|
||
{"101201501", "天门", "天门", "湖北"},
|
||
{"101201601", "仙桃", "仙桃", "湖北"},
|
||
{"101201701", "潜江", "潜江", "湖北"},
|
||
{"101250101", "长沙", "长沙", "湖南"},
|
||
{"101250201", "湘潭", "湘潭", "湖南"},
|
||
{"101250301", "株洲", "株洲", "湖南"},
|
||
{"101250401", "衡阳", "衡阳", "湖南"},
|
||
{"101250501", "郴州", "郴州", "湖南"},
|
||
{"101250601", "常德", "常德", "湖南"},
|
||
{"101250700", "益阳", "益阳", "湖南"},
|
||
{"101250801", "娄底", "娄底", "湖南"},
|
||
{"101250901", "邵阳", "邵阳", "湖南"},
|
||
{"101251001", "岳阳", "岳阳", "湖南"},
|
||
{"101251101", "张家界", "张家界", "湖南"},
|
||
{"101251201", "怀化", "怀化", "湖南"},
|
||
{"101251401", "永州", "永州", "湖南"},
|
||
{"101251501", "湘西土家族苗族自治州", "湘西土家族苗族自治州", "湖南"},
|
||
{"101280101", "广州", "广州", "广东"},
|
||
{"101280201", "韶关", "韶关", "广东"},
|
||
{"101280301", "惠州", "惠州", "广东"},
|
||
{"101280401", "梅州", "梅州", "广东"},
|
||
{"101280501", "汕头", "汕头", "广东"},
|
||
{"101280601", "深圳", "深圳", "广东"},
|
||
{"101280701", "珠海", "珠海", "广东"},
|
||
{"101280800", "佛山", "佛山", "广东"},
|
||
{"101280901", "肇庆", "肇庆", "广东"},
|
||
{"101281001", "湛江", "湛江", "广东"},
|
||
{"101281101", "江门", "江门", "广东"},
|
||
{"101281201", "河源", "河源", "广东"},
|
||
{"101281301", "清远", "清远", "广东"},
|
||
{"101281401", "云浮", "云浮", "广东"},
|
||
{"101281501", "潮州", "潮州", "广东"},
|
||
{"101281601", "东莞", "东莞", "广东"},
|
||
{"101281701", "中山", "中山", "广东"},
|
||
{"101281801", "阳江", "阳江", "广东"},
|
||
{"101281901", "揭阳", "揭阳", "广东"},
|
||
{"101282001", "茂名", "茂名", "广东"},
|
||
{"101282101", "汕尾", "汕尾", "广东"},
|
||
{"101310101", "海口", "海口", "海南"},
|
||
{"101310201", "三亚", "三亚", "海南"},
|
||
{"101310202", "东方", "东方", "海南"},
|
||
{"101310203", "临高县", "临高县", "海南"},
|
||
{"101310204", "澄迈县", "澄迈县", "海南"},
|
||
{"101310205", "儋州", "儋州", "海南"},
|
||
{"101310206", "昌江黎族自治县", "昌江黎族自治县", "海南"},
|
||
{"101310207", "白沙黎族自治县", "白沙黎族自治县", "海南"},
|
||
{"101310208", "琼中黎族苗族自治县", "琼中黎族苗族自治县", "海南"},
|
||
{"101310209", "定安县", "定安县", "海南"},
|
||
{"101310210", "屯昌县", "屯昌县", "海南"},
|
||
{"101310211", "琼海", "琼海", "海南"},
|
||
{"101310212", "文昌", "文昌", "海南"},
|
||
{"101310214", "保亭黎族苗族自治县", "保亭黎族苗族自治县", "海南"},
|
||
{"101310215", "万宁", "万宁", "海南"},
|
||
{"101310216", "陵水黎族自治县", "陵水黎族自治县", "海南"},
|
||
{"101310221", "乐东黎族自治县", "乐东黎族自治县", "海南"},
|
||
{"101310222", "五指山", "五指山", "海南"},
|
||
{"101310301", "三沙", "三沙", "海南"},
|
||
{"101270101", "成都", "成都", "四川"},
|
||
{"101270201", "攀枝花", "攀枝花", "四川"},
|
||
{"101270301", "自贡", "自贡", "四川"},
|
||
{"101270401", "绵阳", "绵阳", "四川"},
|
||
{"101270501", "南充", "南充", "四川"},
|
||
{"101270601", "达州", "达州", "四川"},
|
||
{"101270701", "遂宁", "遂宁", "四川"},
|
||
{"101270801", "广安", "广安", "四川"},
|
||
{"101270901", "巴中", "巴中", "四川"},
|
||
{"101271001", "泸州", "泸州", "四川"},
|
||
{"101271101", "宜宾", "宜宾", "四川"},
|
||
{"101271201", "内江", "内江", "四川"},
|
||
{"101271301", "资阳", "资阳", "四川"},
|
||
{"101271401", "乐山", "乐山", "四川"},
|
||
{"101271501", "眉山", "眉山", "四川"},
|
||
{"101271601", "凉山彝族自治州", "凉山彝族自治州", "四川"},
|
||
{"101271701", "雅安", "雅安", "四川"},
|
||
{"101271801", "甘孜藏族自治州", "甘孜藏族自治州", "四川"},
|
||
{"101271901", "阿坝藏族羌族自治州", "阿坝藏族羌族自治州", "四川"},
|
||
{"101272001", "德阳", "德阳", "四川"},
|
||
{"101272101", "广元", "广元", "四川"},
|
||
{"101260101", "贵阳", "贵阳", "贵州"},
|
||
{"101260201", "遵义", "遵义", "贵州"},
|
||
{"101260301", "安顺", "安顺", "贵州"},
|
||
{"101260401", "黔南布依族苗族自治州", "黔南布依族苗族自治州", "贵州"},
|
||
{"101260501", "黔东南苗族侗族自治州", "黔东南苗族侗族自治州", "贵州"},
|
||
{"101260601", "铜仁", "铜仁", "贵州"},
|
||
{"101260701", "毕节", "毕节", "贵州"},
|
||
{"101260801", "六盘水", "六盘水", "贵州"},
|
||
{"101260901", "黔西南布依族苗族自治州", "黔西南布依族苗族自治州", "贵州"},
|
||
{"101290101", "昆明", "昆明", "云南"},
|
||
{"101290201", "大理白族自治州", "大理白族自治州", "云南"},
|
||
{"101290301", "红河哈尼族彝族自治州", "红河哈尼族彝族自治州", "云南"},
|
||
{"101290401", "曲靖", "曲靖", "云南"},
|
||
{"101290501", "保山", "保山", "云南"},
|
||
{"101290601", "文山壮族苗族自治州", "文山壮族苗族自治州", "云南"},
|
||
{"101290701", "玉溪", "玉溪", "云南"},
|
||
{"101290801", "楚雄彝族自治州", "楚雄彝族自治州", "云南"},
|
||
{"101290901", "普洱", "普洱", "云南"},
|
||
{"101291001", "昭通", "昭通", "云南"},
|
||
{"101291101", "临沧", "临沧", "云南"},
|
||
{"101291201", "怒江傈僳族自治州", "怒江傈僳族自治州", "云南"},
|
||
{"101291301", "迪庆藏族自治州", "迪庆藏族自治州", "云南"},
|
||
{"101291401", "丽江", "丽江", "云南"},
|
||
{"101291501", "德宏傣族景颇族自治州", "德宏傣族景颇族自治州", "云南"},
|
||
{"101291601", "西双版纳傣族自治州", "西双版纳傣族自治州", "云南"},
|
||
{"101110101", "西安", "西安", "陕西"},
|
||
{"101110200", "咸阳", "咸阳", "陕西"},
|
||
{"101110300", "延安", "延安", "陕西"},
|
||
{"101110401", "榆林", "榆林", "陕西"},
|
||
{"101110501", "渭南", "渭南", "陕西"},
|
||
{"101110601", "商洛", "商洛", "陕西"},
|
||
{"101110701", "安康", "安康", "陕西"},
|
||
{"101110801", "汉中", "汉中", "陕西"},
|
||
{"101110901", "宝鸡", "宝鸡", "陕西"},
|
||
{"101111001", "铜川", "铜川", "陕西"},
|
||
{"101160101", "兰州", "兰州", "甘肃"},
|
||
{"101160201", "定西", "定西", "甘肃"},
|
||
{"101160301", "平凉", "平凉", "甘肃"},
|
||
{"101160401", "庆阳", "庆阳", "甘肃"},
|
||
{"101160501", "武威", "武威", "甘肃"},
|
||
{"101160601", "金昌", "金昌", "甘肃"},
|
||
{"101160701", "张掖", "张掖", "甘肃"},
|
||
{"101160801", "酒泉", "酒泉", "甘肃"},
|
||
{"101160901", "天水", "天水", "甘肃"},
|
||
{"101161001", "陇南", "陇南", "甘肃"},
|
||
{"101161101", "临夏回族自治州", "临夏回族自治州", "甘肃"},
|
||
{"101161201", "甘南藏族自治州", "甘南藏族自治州", "甘肃"},
|
||
{"101161301", "白银", "白银", "甘肃"},
|
||
{"101161401", "嘉峪关", "嘉峪关", "甘肃"},
|
||
{"101150101", "西宁", "西宁", "青海"},
|
||
{"101150201", "海东", "海东", "青海"},
|
||
{"101150301", "黄南藏族自治州", "黄南藏族自治州", "青海"},
|
||
{"101150401", "海南藏族自治州", "海南藏族自治州", "青海"},
|
||
{"101150501", "果洛藏族自治州", "果洛藏族自治州", "青海"},
|
||
{"101150601", "玉树藏族自治州", "玉树藏族自治州", "青海"},
|
||
{"101150701", "海西蒙古族藏族自治州", "海西蒙古族藏族自治州", "青海"},
|
||
{"101150801", "海北藏族自治州", "海北藏族自治州", "青海"},
|
||
{"101080101", "呼和浩特", "呼和浩特", "内蒙古"},
|
||
{"101080201", "包头", "包头", "内蒙古"},
|
||
{"101080301", "乌海", "乌海", "内蒙古"},
|
||
{"101080401", "乌兰察布", "乌兰察布", "内蒙古"},
|
||
{"101080501", "通辽", "通辽", "内蒙古"},
|
||
{"101080601", "赤峰", "赤峰", "内蒙古"},
|
||
{"101080701", "鄂尔多斯", "鄂尔多斯", "内蒙古"},
|
||
{"101080801", "巴彦淖尔", "巴彦淖尔", "内蒙古"},
|
||
{"101080901", "锡林郭勒盟", "锡林郭勒盟", "内蒙古"},
|
||
{"101081001", "呼伦贝尔", "呼伦贝尔", "内蒙古"},
|
||
{"101081101", "兴安盟", "兴安盟", "内蒙古"},
|
||
{"101081201", "阿拉善盟", "阿拉善盟", "内蒙古"},
|
||
{"101300101", "南宁", "南宁", "广西"},
|
||
{"101300201", "崇左", "崇左", "广西"},
|
||
{"101300301", "柳州", "柳州", "广西"},
|
||
{"101300401", "来宾", "来宾", "广西"},
|
||
{"101300501", "桂林", "桂林", "广西"},
|
||
{"101300601", "梧州", "梧州", "广西"},
|
||
{"101300701", "贺州", "贺州", "广西"},
|
||
{"101300801", "贵港", "贵港", "广西"},
|
||
{"101300901", "玉林", "玉林", "广西"},
|
||
{"101301001", "百色", "百色", "广西"},
|
||
{"101301101", "钦州", "钦州", "广西"},
|
||
{"101301201", "河池", "河池", "广西"},
|
||
{"101301301", "北海", "北海", "广西"},
|
||
{"101301401", "防城港", "防城港", "广西"},
|
||
{"101140101", "拉萨", "拉萨", "西藏"},
|
||
{"101140201", "日喀则", "日喀则", "西藏"},
|
||
{"101140301", "山南", "山南", "西藏"},
|
||
{"101140401", "林芝", "林芝", "西藏"},
|
||
{"101140501", "昌都", "昌都", "西藏"},
|
||
{"101140601", "那曲", "那曲", "西藏"},
|
||
{"101140701", "阿里地区", "阿里地区", "西藏"},
|
||
{"101170101", "银川", "银川", "宁夏"},
|
||
{"101170201", "石嘴山", "石嘴山", "宁夏"},
|
||
{"101170301", "吴忠", "吴忠", "宁夏"},
|
||
{"101170401", "固原", "固原", "宁夏"},
|
||
{"101170501", "中卫", "中卫", "宁夏"},
|
||
{"101130101", "乌鲁木齐", "乌鲁木齐", "新疆"},
|
||
{"101130201", "克拉玛依", "克拉玛依", "新疆"},
|
||
{"101130301", "石河子", "石河子", "新疆"},
|
||
{"101130401", "昌吉回族自治州", "昌吉回族自治州", "新疆"},
|
||
{"101130501", "吐鲁番", "吐鲁番", "新疆"},
|
||
{"101130601", "巴音郭楞蒙古自治州", "巴音郭楞蒙古自治州", "新疆"},
|
||
{"101130701", "阿拉尔", "阿拉尔", "新疆"},
|
||
{"101130801", "阿克苏地区", "阿克苏地区", "新疆"},
|
||
{"101130901", "喀什地区", "喀什地区", "新疆"},
|
||
{"101131001", "伊犁哈萨克自治州", "伊犁哈萨克自治州", "新疆"},
|
||
{"101131101", "塔城地区", "塔城地区", "新疆"},
|
||
{"101131201", "哈密", "哈密", "新疆"},
|
||
{"101131301", "和田地区", "和田地区", "新疆"},
|
||
{"101131401", "阿勒泰地区", "阿勒泰地区", "新疆"},
|
||
{"101131501", "克孜勒苏柯尔克孜自治州", "克孜勒苏柯尔克孜自治州", "新疆"},
|
||
{"101131601", "博尔塔拉蒙古自治州", "博尔塔拉蒙古自治州", "新疆"},
|
||
{"101131701", "图木舒克", "图木舒克", "新疆"},
|
||
{"101131801", "五家渠", "五家渠", "新疆"},
|
||
{"101131901", "铁门关", "铁门关", "新疆"},
|
||
{"101131920", "昆玉", "昆玉", "新疆"},
|
||
{"101132101", "北屯", "北屯", "新疆"},
|
||
{"101132201", "双河", "双河", "新疆"},
|
||
{"101132301", "可克达拉", "可克达拉", "新疆"},
|
||
{"101132401", "胡杨河", "胡杨河", "新疆"},
|
||
{"101132501", "新星", "新星", "新疆"},
|
||
{"101132601", "白杨", "白杨", "新疆"},
|
||
{"101320101", "香港特别行政区", "香港特别行政区", "香港"},
|
||
{"101330101", "澳门特别行政区", "澳门特别行政区", "澳门"},
|
||
{"101231001", "宜兰县", "宜兰县", "台湾"},
|
||
{"101340101", "台北", "台北", "台湾"},
|
||
{"101340102", "桃园", "桃园", "台湾"},
|
||
{"101340103", "新竹", "新竹", "台湾"},
|
||
{"101340201", "高雄", "高雄", "台湾"},
|
||
{"101340202", "嘉义", "嘉义", "台湾"},
|
||
{"101340203", "台南", "台南", "台湾"},
|
||
{"101340204", "台东县", "台东县", "台湾"},
|
||
{"101340205", "屏东县", "屏东县", "台湾"},
|
||
{"101340401", "台中", "台中", "台湾"},
|
||
{"101340402", "苗栗县", "苗栗县", "台湾"},
|
||
{"101340403", "彰化县", "彰化县", "台湾"},
|
||
{"101340404", "南投县", "南投县", "台湾"},
|
||
{"101340405", "花莲县", "花莲县", "台湾"},
|
||
}
|
||
|
||
var defaultCity = City{"101200101", "武汉", "武汉", "湖北"}
|
||
|
||
var httpClient = &http.Client{Timeout: 60 * time.Second}
|
||
|
||
var weatherIcons = map[string]string{
|
||
"晴": "☀️", "多云": "⛅", "阴": "☁️",
|
||
"雨": "🌧️", "雪": "❄️", "雷": "⛈️",
|
||
"雾": "🌫️", "霾": "😷", "风": "💨",
|
||
}
|
||
|
||
func getWeatherIcon(text string) string {
|
||
for k, v := range weatherIcons {
|
||
if strings.Contains(text, k) {
|
||
return v
|
||
}
|
||
}
|
||
return "🌤️"
|
||
}
|
||
|
||
func httpGet(url string) ([]byte, error) {
|
||
resp, err := httpClient.Get(url)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer resp.Body.Close()
|
||
return io.ReadAll(resp.Body)
|
||
}
|
||
|
||
// ── 定位 ──
|
||
|
||
func getLocation() City {
|
||
if city := locateByIPIP(); city != nil {
|
||
return *city
|
||
}
|
||
if city := locateByGeoAPI(); city != nil {
|
||
return *city
|
||
}
|
||
log.Println("所有定位失败,使用默认城市:", defaultCity.Name)
|
||
return defaultCity
|
||
}
|
||
|
||
var (
|
||
reIPIP = regexp.MustCompile(`来自于[::]\s*(.+?)\s*$`)
|
||
reIPAddr = regexp.MustCompile(`(\d+\.\d+\.\d+\.\d+)`)
|
||
)
|
||
|
||
func locateByIPIP() *City {
|
||
data, err := httpGet("https://myip.ipip.net")
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
text := string(data)
|
||
m := reIPIP.FindStringSubmatch(text)
|
||
if m == nil {
|
||
return nil
|
||
}
|
||
parts := strings.Fields(m[1])
|
||
cityName := parts[0]
|
||
if len(parts) >= 3 {
|
||
cityName = parts[2]
|
||
} else if len(parts) >= 2 {
|
||
cityName = parts[1]
|
||
}
|
||
for _, c := range cities {
|
||
if c.Name == cityName {
|
||
log.Println("ipip.net 匹配:", c.Name)
|
||
return &c
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func locateByGeoAPI() *City {
|
||
data, err := httpGet("https://myip.ipip.net")
|
||
if err == nil {
|
||
if m := reIPAddr.FindStringSubmatch(string(data)); m != nil {
|
||
return geoLookup(m[1])
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func geoLookup(ip string) *City {
|
||
key := loadConfig().qweatherKey()
|
||
if key == "" {
|
||
log.Println("未配置和风天气 API Key")
|
||
return nil
|
||
}
|
||
url := fmt.Sprintf(qweatherHost+"/v2/city/lookup?location=%s&key=%s", ip, key)
|
||
data, err := httpGet(url)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
var resp struct {
|
||
Code string `json:"code"`
|
||
Location []struct {
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
Adm2 string `json:"adm2"`
|
||
Adm1 string `json:"adm1"`
|
||
} `json:"location"`
|
||
}
|
||
if json.Unmarshal(data, &resp) != nil || resp.Code != "200" || len(resp.Location) == 0 {
|
||
return nil
|
||
}
|
||
loc := resp.Location[0]
|
||
for _, c := range cities {
|
||
if c.Name == loc.Name {
|
||
return &c
|
||
}
|
||
}
|
||
return &City{loc.ID, loc.Name, loc.Adm2, loc.Adm1}
|
||
}
|
||
|
||
func getCurrentCity() City {
|
||
cfg := loadConfig()
|
||
if cfg.City != "" {
|
||
for _, c := range cities {
|
||
if c.ID == cfg.City {
|
||
return c
|
||
}
|
||
}
|
||
}
|
||
return getLocation()
|
||
}
|
||
|
||
// ── 天气数据 ──
|
||
|
||
type hourlyItem struct {
|
||
Time string `json:"time"`
|
||
Temp string `json:"temp"`
|
||
Icon string `json:"icon"`
|
||
Pop string `json:"pop,omitempty"`
|
||
}
|
||
|
||
type dailyItem struct {
|
||
Date string `json:"date"`
|
||
Icon string `json:"icon"`
|
||
TempMin string `json:"tempMin"`
|
||
TempMax string `json:"tempMax"`
|
||
}
|
||
|
||
type currentWeather struct {
|
||
Text string `json:"text"`
|
||
Temp string `json:"temp"`
|
||
}
|
||
|
||
var (
|
||
weatherCacheMu sync.Mutex
|
||
weatherCache map[string]time.Time
|
||
weatherCacheData map[string]string
|
||
)
|
||
|
||
func init() {
|
||
weatherCache = make(map[string]time.Time)
|
||
weatherCacheData = make(map[string]string)
|
||
}
|
||
|
||
func fetchAndPushWeather(city City) {
|
||
// 5 分钟内同一城市不重复请求
|
||
weatherCacheMu.Lock()
|
||
if last, ok := weatherCache[city.ID]; ok && time.Since(last) < 5*time.Minute {
|
||
if cached, ok := weatherCacheData[city.ID]; ok {
|
||
weatherCacheMu.Unlock()
|
||
evalJS(fmt.Sprintf(`if(window.updateWeatherFromGo) window.updateWeatherFromGo(%s)`, cached))
|
||
return
|
||
}
|
||
}
|
||
weatherCacheMu.Unlock()
|
||
|
||
type weatherData struct {
|
||
Current string `json:"current"`
|
||
Hourly []hourlyItem `json:"hourly"`
|
||
Daily []dailyItem `json:"daily"`
|
||
City string `json:"city"`
|
||
}
|
||
|
||
wd := weatherData{}
|
||
cityName := city.Name
|
||
if city.Adm2 != city.Name {
|
||
cityName = city.Adm2 + " " + city.Name
|
||
}
|
||
wd.City = cityName
|
||
|
||
if now := fetchCurrentWeather(city.ID); now != nil {
|
||
icon := getWeatherIcon(now.Text)
|
||
wd.Current = fmt.Sprintf("📍 %s %s %s %s°C", cityName, icon, now.Text, now.Temp)
|
||
} else {
|
||
wd.Current = fmt.Sprintf("📍 %s ☀️ 晴 24°C", cityName)
|
||
}
|
||
|
||
wd.Hourly = fetchHourlyForecast(city.ID)
|
||
wd.Daily = fetchDailyForecast(city.ID)
|
||
|
||
jsonData, _ := json.Marshal(wd)
|
||
weatherCacheMu.Lock()
|
||
weatherCache[city.ID] = time.Now()
|
||
weatherCacheData[city.ID] = string(jsonData)
|
||
weatherCacheMu.Unlock()
|
||
evalJS(fmt.Sprintf(`if(window.updateWeatherFromGo) window.updateWeatherFromGo(%s)`, string(jsonData)))
|
||
log.Println("天气已推送:", wd.Current)
|
||
}
|
||
|
||
func fetchCurrentWeather(cityID string) *currentWeather {
|
||
key := loadConfig().qweatherKey()
|
||
if key == "" {
|
||
log.Println("未配置和风天气 API Key")
|
||
return nil
|
||
}
|
||
url := fmt.Sprintf(qweatherHost+"/v7/weather/now?location=%s&key=%s", cityID, key)
|
||
data, err := httpGet(url)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
var resp struct {
|
||
Code string `json:"code"`
|
||
Now struct {
|
||
Text string `json:"text"`
|
||
Temp string `json:"temp"`
|
||
} `json:"now"`
|
||
}
|
||
if json.Unmarshal(data, &resp) != nil || resp.Code != "200" {
|
||
return nil
|
||
}
|
||
return ¤tWeather{resp.Now.Text, resp.Now.Temp}
|
||
}
|
||
|
||
func fetchHourlyForecast(cityID string) []hourlyItem {
|
||
key := loadConfig().qweatherKey()
|
||
if key == "" {
|
||
return nil
|
||
}
|
||
url := fmt.Sprintf(qweatherHost+"/v7/weather/24h?location=%s&key=%s", cityID, key)
|
||
data, err := httpGet(url)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
var resp struct {
|
||
Code string `json:"code"`
|
||
Hourly []struct {
|
||
FxTime string `json:"fxTime"`
|
||
Temp string `json:"temp"`
|
||
Pop string `json:"pop"`
|
||
Text string `json:"text"`
|
||
} `json:"hourly"`
|
||
}
|
||
if json.Unmarshal(data, &resp) != nil || resp.Code != "200" {
|
||
return nil
|
||
}
|
||
var result []hourlyItem
|
||
for i, h := range resp.Hourly {
|
||
if i >= 8 {
|
||
break
|
||
}
|
||
t := h.FxTime
|
||
if idx := strings.Index(t, "T"); idx >= 0 {
|
||
t = t[idx+1:]
|
||
if len(t) >= 5 {
|
||
t = t[:5]
|
||
}
|
||
}
|
||
result = append(result, hourlyItem{t, h.Temp + "°", getWeatherIcon(h.Text), h.Pop})
|
||
}
|
||
return result
|
||
}
|
||
|
||
func fetchDailyForecast(cityID string) []dailyItem {
|
||
key := loadConfig().qweatherKey()
|
||
if key == "" {
|
||
return nil
|
||
}
|
||
url := fmt.Sprintf(qweatherHost+"/v7/weather/7d?location=%s&key=%s", cityID, key)
|
||
data, err := httpGet(url)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
var resp struct {
|
||
Code string `json:"code"`
|
||
Daily []struct {
|
||
FxDate string `json:"fxDate"`
|
||
TextDay string `json:"textDay"`
|
||
TempMin string `json:"tempMin"`
|
||
TempMax string `json:"tempMax"`
|
||
} `json:"daily"`
|
||
}
|
||
if json.Unmarshal(data, &resp) != nil || resp.Code != "200" {
|
||
return nil
|
||
}
|
||
weekdays := []string{"周日", "周一", "周二", "周三", "周四", "周五", "周六"}
|
||
today := time.Now().Format("2006-01-02")
|
||
var result []dailyItem
|
||
for _, d := range resp.Daily {
|
||
date := d.FxDate
|
||
if date == today {
|
||
date = "今天"
|
||
} else {
|
||
if t, err := time.Parse("2006-01-02", date); err == nil {
|
||
date = weekdays[t.Weekday()]
|
||
}
|
||
}
|
||
result = append(result, dailyItem{date, getWeatherIcon(d.TextDay), d.TempMin, d.TempMax})
|
||
}
|
||
return result
|
||
}
|
||
|
||
func weatherLoop() {
|
||
time.Sleep(3 * time.Second)
|
||
city := getCurrentCity()
|
||
log.Println("初始城市:", city.Name)
|
||
fetchAndPushWeather(city)
|
||
|
||
ticker := time.NewTicker(10 * time.Minute)
|
||
for range ticker.C {
|
||
city := getCurrentCity()
|
||
fetchAndPushWeather(city)
|
||
}
|
||
}
|