From 9ae29cbff097c9009c5aedf71494b06d3878846a Mon Sep 17 00:00:00 2001 From: root Date: Sat, 21 Mar 2026 18:14:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=A4=A9=E6=B0=94=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E4=B8=BA=E5=92=8C=E9=A3=8E=E5=A4=A9=E6=B0=94=EF=BC=8C?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=A4=A9=E6=B0=94=E5=9F=8E=E5=B8=82=E4=B8=8E?= =?UTF-8?q?=20API=20Key=20=E9=85=8D=E7=BD=AE=EF=BC=8C=E7=AE=80=E5=8C=96?= =?UTF-8?q?=E4=B8=BB=E9=A2=98=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 13 +- .../HarmonyHues/assets/weather_cache.json | 2 +- .../components/widgets/widget-weather.php | 296 +++++++++++++----- usr/themes/HarmonyHues/core/theme-config.php | 22 +- 4 files changed, 233 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index b8afe7b..9014bf0 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,15 @@ Typecho 系统运行时代码库: * 加入 OpenWeatherMap 图标,优化字体、行高、布局与对齐,使输出居中且更协调。 * 注释和调试输出更详细,方便开发者阅读与排查。 +- 2026-03-21:切换默认天气接口为和风天气(QWeather)。 + * 为提高国内访问稳定性,主题已将默认天气请求改为使用和风天气(QWeather)API(v7 now)。 + * 请到 https://dev.qweather.com/ 注册并获取 API Key,然后在后台主题设置的“天气 API Key”项中填写。 + * 如果暂时不希望调用网络天气接口,可在后台关闭侧边栏“天气”模块以加速页面加载。 + + 注意:和风天气已启用 API Host 功能,公共域名(如 devapi.qweather.com)将逐步停止服务。如果你在控制台配置了 API Host,请在主题设置的“天气 API Host”中填写你的 Host(例如:p66mtfux4j.re.qweatherapi.com)。 + ### 注意事项 -- 请使用 **有效的天气 API Key**。上述 key `464032d12749b7c0df937df0215af884` 返回错误“Invalid API key”,说明并非 OpenWeatherMap 认可的密钥。访问 https://openweathermap.org/ 获取或检查你的帐号密钥。 -- 也可以替换为其他天气服务(和风、心知等),只需改写 `widget-weather.php` 中的请求 URL 和参数。 -- 错误信息会在侧边栏显示便于调试。 \ No newline at end of file +- 请使用 **有效的天气 API Key**。推荐使用和风天气(QWeather);获取与使用请参考:https://dev.qweather.com/ 。 +- 如果你更倾向于无需注册的快速方案,可使用 `tianqiapi.com` 作为备用接口,但稳定性与功能可能有限。 +- 错误信息会在侧边栏以注释或文本形式显示,便于调试。 \ No newline at end of file diff --git a/usr/themes/HarmonyHues/assets/weather_cache.json b/usr/themes/HarmonyHues/assets/weather_cache.json index 1be5525..5033bb8 100644 --- a/usr/themes/HarmonyHues/assets/weather_cache.json +++ b/usr/themes/HarmonyHues/assets/weather_cache.json @@ -1 +1 @@ -{"city":"Guangzhou","timestamp":1774074655,"data":{"coord":{"lon":113.25,"lat":23.1167},"weather":[{"id":803,"main":"Clouds","description":"\u591a\u4e91","icon":"04d"}],"base":"stations","main":{"temp":25.38,"feels_like":25.46,"temp_min":25.38,"temp_max":25.38,"pressure":1015,"humidity":57,"sea_level":1015,"grnd_level":1015},"visibility":10000,"wind":{"speed":3.89,"deg":182,"gust":3.83},"clouds":{"all":84},"dt":1774074242,"sys":{"country":"CN","sunrise":1774045833,"sunset":1774089483},"timezone":28800,"id":1809858,"name":"Guangzhou","cod":200}} \ No newline at end of file +{"city":"Guangzhou","timestamp":1774087093,"data":{"code":"200","updateTime":"2026-03-21T17:53+08:00","fxLink":"https:\/\/www.qweather.com\/weather\/guangzhou-101280101.html","now":{"obsTime":"2026-03-21T17:50+08:00","temp":"21","feelsLike":"23","icon":"501","text":"\u96fe","wind360":"9","windDir":"\u5317\u98ce","windScale":"0","windSpeed":"1","humidity":"92","precip":"0.0","pressure":"1007","vis":"2","cloud":"100","dew":"19"},"refer":{"sources":["QWeather"],"license":["QWeather Developers License"]}}} \ No newline at end of file diff --git a/usr/themes/HarmonyHues/components/widgets/widget-weather.php b/usr/themes/HarmonyHues/components/widgets/widget-weather.php index b07cddd..d12d630 100644 --- a/usr/themes/HarmonyHues/components/widgets/widget-weather.php +++ b/usr/themes/HarmonyHues/components/widgets/widget-weather.php @@ -13,98 +13,244 @@ if ( ! defined('__TYPECHO_ROOT_DIR__')) { // 只有在侧边栏开启且不是文章页面时显示 if ( ! empty($this->options->sidebarBlock) && in_array('ShowSidebarWeather', $this->options->sidebarBlock) && ! $this->is('post')): ?> options->weatherCity ?: 'Beijing'); -$apiKey = trim($this->options->weatherApiKey ?: ''); +$city = 'Guangzhou'; +$apiKey = '71215dad0abc4250897761e3fa8ca796'; +$apiHost = 'p66mtfux4j.re.qweatherapi.com'; +$cacheNote = '注意:API Key/Host 已硬编码在主题文件中,如需更换请编辑 components/widgets/widget-weather.php'; $cacheFile = dirname(__DIR__, 2) . '/assets/weather_cache.json'; // 缓存机制:记录城市和时间,过期 1 小时,城市变更时自动失效 -function hh_fetch_weather($city, $apiKey, $cacheFile) { - if (file_exists($cacheFile)) { - $raw = @file_get_contents($cacheFile); - $cached = $raw ? json_decode($raw, true) : null; - // 缓存结构:{"city":"xxx","timestamp":12345678,"data":{…}} - if ($cached && isset($cached['city'], $cached['timestamp'], $cached['data'])) { - if ($cached['city'] === $city && time() - $cached['timestamp'] < 3600) { - return $cached['data']; - } - } +function hh_fetch_weather($city, $apiKey, $cacheFile, $apiHost = '') { + if (file_exists($cacheFile)) { + $raw = @file_get_contents($cacheFile); + $cached = $raw ? json_decode($raw, true) : null; + // 缓存结构:{"city":"xxx","timestamp":12345678,"data":{…}} + if ($cached && isset($cached['city'], $cached['timestamp'], $cached['data'])) { + if ($cached['city'] === $city && time() - $cached['timestamp'] < 3600) { + return $cached['data']; + } } + } - if (empty($apiKey) || empty($city)) { - return null; - } - - $url = 'https://api.openweathermap.org/data/2.5/weather?q=' . urlencode($city) - . '&appid=' . $apiKey . '&units=metric&lang=zh_cn'; - $json = @file_get_contents($url); - if ($json) { - $payload = json_encode([ - 'city' => $city, - 'timestamp' => time(), - 'data' => json_decode($json, true), - ]); - @file_put_contents($cacheFile, $payload); - return json_decode($json, true); - } + if (empty($apiKey) || empty($city)) { return null; + } + + // 使用和风天气(QWeather)API:v7 now,使用自定义 API Host 替换公共域名 + $host = !empty($apiHost) ? $apiHost : 'devapi.qweather.com'; + + // 如果 city 不是纯数字(city id),先使用 Geo API 查找城市 ID(geo/v2/city/lookup) + $location = $city; + if (!preg_match('/^\d+$/', $city)) { + $geoUrl = 'https://' . $host . '/geo/v2/city/lookup?location=' . urlencode($city) . '&key=' . $apiKey; + $geoJson = false; + if (function_exists('curl_init')) { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $geoUrl); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_ENCODING, ''); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); + curl_setopt($ch, CURLOPT_TIMEOUT, 6); + curl_setopt($ch, CURLOPT_USERAGENT, 'HarmonyHues-Weather/1.0'); + $geoJson = curl_exec($ch); + $geo_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + if ($geoJson === false || intval($geo_code) !== 200) { + $geoJson = false; + } + } else { + $ctx = stream_context_create(['http' => ['timeout' => 6, 'method' => 'GET']]); + $geoJson = @file_get_contents($geoUrl, false, $ctx); + } + if ($geoJson) { + $geoData = json_decode($geoJson, true); + if (isset($geoData['location'][0]['id'])) { + $location = $geoData['location'][0]['id']; + } + } + } + + $url = 'https://' . $host . '/v7/weather/now?location=' . urlencode($location) . '&key=' . $apiKey . '&lang=zh'; + + // 优先使用 cURL,以支持连接/读取超时 + $json = false; + if (function_exists('curl_init')) { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_ENCODING, ''); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); // 连接超时 3s + curl_setopt($ch, CURLOPT_TIMEOUT, 6); // 总超时 6s + curl_setopt($ch, CURLOPT_USERAGENT, 'HarmonyHues-Weather/1.0'); + $json = curl_exec($ch); + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + if ($json === false || intval($http_code) !== 200) { + $json = false; + } + } else { + // 回退到 file_get_contents,但设置默认的 socket 超时 + $ctx = stream_context_create([ + 'http' => [ + 'timeout' => 6, + 'method' => 'GET', + 'header' => "User-Agent: HarmonyHues-Weather/1.0\r\n" + ] + ]); + $json = @file_get_contents($url, false, $ctx); + } + + if ($json) { + $data = json_decode($json, true); + // 和风返回示例:{"code":"200","updateTime":"...","now":{...}} + if (isset($data['code']) && ($data['code'] === '200' || $data['code'] === 200)) { + $payload = json_encode([ + 'city' => $city, + 'timestamp' => time(), + 'data' => $data, + ]); + @file_put_contents($cacheFile, $payload); + return $data; + } + } + return null; } -$weather = hh_fetch_weather($city, $apiKey, $cacheFile); -// 用于调试:在 HTML 注释中输出参数和值,方便查看 -$reqUrl = 'https://api.openweathermap.org/data/2.5/weather?q=' . urlencode($city) - . '&appid=' . $apiKey . '&units=metric&lang=zh_cn'; +$weather = hh_fetch_weather($city, $apiKey, $cacheFile, $apiHost); +// 用于调试:记录请求 URL(和风天气) +$reqUrl = 'https://' . $apiHost . '/v7/weather/now?location=' . urlencode($city) + . '&key=' . $apiKey . '&lang=zh'; ?> + false, + 'city' => null, + 'description' => null, + 'temp' => null, + 'feels_like' => null, + 'humidity' => null, + 'pressure' => null, + 'wind_speed' => null, + 'wind_deg' => null, + 'wind_dir' => null, + 'wind_scale' => null, + 'icon_code' => null, + 'precip' => null, + 'vis' => null, + 'cloud' => null, + 'dew' => null, + 'obsTime' => null, + 'refer' => null, + 'raw' => $raw, + ]; + if (!is_array($raw)) return $out; + + // QWeather 格式 + if (isset($raw['now'])) { + $now = $raw['now']; + $out['ok'] = (isset($raw['code']) && ($raw['code'] === '200' || $raw['code'] === 200)); + // 某些 Host 返回 location 字段,某些返回 location 数组 + if (isset($raw['location']) && is_array($raw['location'])) { + $out['city'] = $raw['location'][0]['name'] ?? $raw['location'][0]['adm2'] ?? null; + } else { + $out['city'] = $raw['location'] ?? null; + } + $out['description'] = $now['text'] ?? null; + $out['temp'] = $now['temp'] ?? null; + $out['feels_like'] = $now['feelsLike'] ?? null; + $out['humidity'] = $now['humidity'] ?? null; + $out['pressure'] = $now['pressure'] ?? null; + $out['wind_speed'] = $now['windSpeed'] ?? null; + $out['wind_dir'] = $now['windDir'] ?? ($now['wind360'] ?? null); + $out['wind_scale'] = $now['windScale'] ?? null; + $out['wind_deg'] = $now['wind360'] ?? null; + $out['icon_code'] = $now['icon'] ?? null; + $out['precip'] = $now['precip'] ?? null; + $out['vis'] = $now['vis'] ?? null; + $out['cloud'] = $now['cloud'] ?? null; + $out['dew'] = $now['dew'] ?? null; + $out['obsTime'] = $now['obsTime'] ?? null; + $out['refer'] = $raw['refer'] ?? null; + return $out; + } + + // OpenWeatherMap 格式 + if (isset($raw['weather'][0]) && isset($raw['main'])) { + $w = $raw['weather'][0]; + $m = $raw['main']; + $out['ok'] = true; + $out['city'] = $raw['name'] ?? null; + $out['description'] = $w['description'] ?? null; + $out['temp'] = $m['temp'] ?? null; + $out['feels_like'] = $m['feels_like'] ?? ($m['feels_like'] ?? null); + $out['humidity'] = $m['humidity'] ?? null; + $out['pressure'] = $m['pressure'] ?? null; + $out['wind_speed'] = $raw['wind']['speed'] ?? null; + $out['wind_deg'] = $raw['wind']['deg'] ?? null; + $out['icon'] = $w['icon'] ?? null; + return $out; + } + + return $out; +} + +$norm = hh_normalize_weather($weather); +// 格式化更新时间为“YYYY-MM-DD HH:MM”便于阅读 +$displayObsTime = '-'; +if (!empty($norm['obsTime'])) { + try { + $dt = new DateTime($norm['obsTime']); + $displayObsTime = $dt->format('Y-m-d H:i'); + } catch (Exception $e) { + $displayObsTime = $norm['obsTime']; + } +} +?>
- - '北京', - 'Guangzhou' => '广州', - 'Shenzhen' => '深圳', - 'Shanghai' => '上海', - 'Chengdu' => '成都', - // 可根据需要继续添加 - ]; - if (isset($map[$inputCity])) { - return $map[$inputCity]; - } - if (isset($map[$weather['name']])) { - return $map[$weather['name']]; - } - return $weather['name']; - } - $displayCity = hh_display_city($city, $weather); - $w = $weather['weather'][0]; - $m = $weather['main']; - $wind = $weather['wind'] ?? []; - $iconUrl = 'https://openweathermap.org/img/wn/' . $w['icon'] . '@2x.png'; - ?> -
- <?php echo htmlspecialchars($w['description'], ENT_QUOTES); ?> -
-

-

°C

+ +
作者的城市天气
+
+
+
+ + + + + + + + + + +
+
+

+

°C

+
+
-
-

体感:°C;湿度:%;气压: hPa

- -

风速: m/s

- + +
+
体感:°C
+
湿度:%
+
气压: hPa
+
风: m/s
+
降水: mm
+
能见度: km
+
云量:%
+
露点:°C
- -

天气接口错误:

+ +
来源:  •  更新时间:
+ -

天气获取失败

+

天气获取失败(请检查 API Key 与网络)

diff --git a/usr/themes/HarmonyHues/core/theme-config.php b/usr/themes/HarmonyHues/core/theme-config.php index 848c59c..1e52dca 100755 --- a/usr/themes/HarmonyHues/core/theme-config.php +++ b/usr/themes/HarmonyHues/core/theme-config.php @@ -239,27 +239,7 @@ function themeConfig($form) { $form->addInput($blogSignageText); $blogSignageText->setAttribute('class', 'setting-content my-3'); - // 天气城市设置 - $weatherCity = new Typecho_Widget_Helper_Form_Element_Text( - 'weatherCity', - NULL, - 'Beijing', - '天气城市(非必填)', - '介绍:填写用于查询天气的城市名称,例如北京或Beijing' - ); - $form->addInput($weatherCity); - $weatherCity->setAttribute('class', 'setting-content my-3'); - - // 天气 API Key - $weatherApiKey = new Typecho_Widget_Helper_Form_Element_Text( - 'weatherApiKey', - NULL, - NULL, - '天气 API Key(非必填)', - '介绍:在此填写 OpenWeatherMap 或其他天气接口的 API Key' - ); - $form->addInput($weatherApiKey); - $weatherApiKey->setAttribute('class', 'setting-content my-3'); + // (已移除天气配置)天气城市与 API Key/Host 已改为直接写入主题代码以便简化部署 // 首页内容显示 $indexBlock = new Typecho_Widget_Helper_Form_Element_Checkbox(