更新天气接口为和风天气,移除天气城市与 API Key 配置,简化主题部署

This commit is contained in:
root
2026-03-21 18:14:06 +08:00
parent 3c38481573
commit 9ae29cbff0
4 changed files with 233 additions and 100 deletions

View File

@@ -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}}
{"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"]}}}

View File

@@ -13,98 +13,244 @@ if ( ! defined('__TYPECHO_ROOT_DIR__')) {
// 只有在侧边栏开启且不是文章页面时显示
if ( ! empty($this->options->sidebarBlock) && in_array('ShowSidebarWeather', $this->options->sidebarBlock) && ! $this->is('post')): ?>
<?php
// 获取配置信息
$city = trim($this->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;
}
// 使用和风天气QWeatherAPIv7 now使用自定义 API Host 替换公共域名
$host = !empty($apiHost) ? $apiHost : 'devapi.qweather.com';
// 如果 city 不是纯数字city id先使用 Geo API 查找城市 IDgeo/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';
?>
<?php
// 统一解析不同天气接口的返回为标准字段
function hh_normalize_weather($raw) {
$out = [
'ok' => 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'];
}
}
?>
<!-- weather widget debug: city=<?php echo htmlspecialchars($city, ENT_QUOTES); ?>, key_len=<?php echo strlen($apiKey); ?>, url=<?php echo htmlspecialchars($reqUrl, ENT_QUOTES); ?>, allow_url_fopen=<?php echo ini_get('allow_url_fopen') ? 'on' : 'off'; ?>, last_err=<?php $e = error_get_last(); echo $e ? htmlspecialchars($e['message'], ENT_QUOTES) : 'none'; ?> -->
<div class="hh-widget mt-3 weather-widget" style="font-size:0.9rem;line-height:1.4;">
<?php if ($weather && isset($weather['weather'][0])): ?>
<?php
// 语言友好的城市显示(中英文互译)
function hh_display_city($inputCity, $weather) {
static $map = [
'Beijing' => '北京',
'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';
?>
<div class="weather-header" style="display:flex;align-items:center;justify-content:center;">
<img src="<?php echo $iconUrl; ?>"
alt="<?php echo htmlspecialchars($w['description'], ENT_QUOTES); ?>"
style="width:48px;height:48px;margin-right:8px;">
<div style="text-align:left;">
<p style="margin:0;font-weight:bold;line-height:1;"><?php echo $displayCity; ?></p>
<p style="margin:0;line-height:1;"><?php echo $w['description']; ?> <?php echo $m['temp']; ?>°C</p>
<?php if ($norm['ok']): ?>
<div style="font-size:0.95rem;font-weight:600;margin-bottom:6px;">作者的城市天气</div>
<div class="weather-header" style="display:flex;align-items:center;justify-content:space-between;">
<div style="display:flex;align-items:center;">
<div style="width:64px;height:64px;display:flex;align-items:center;justify-content:center;margin-right:10px;">
<!-- 简易条件 SVG根据描述选择保持通用 -->
<?php if (stripos($norm['description'] ?? '', '雨') !== false): ?>
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 13a4 4 0 014-4h1" stroke="#2b7cff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M7 9a5 5 0 0110 0" stroke="#2b7cff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 19l1.5-2M12 19l1.5-2M16 19l1.5-2" stroke="#2b7cff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
<?php elseif (stripos($norm['description'] ?? '', '云') !== false || stripos($norm['description'] ?? '', '阴') !== false): ?>
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M20 17.5A4.5 4.5 0 0015.5 13H7a4 4 0 010-8 5 5 0 015 5h.5" stroke="#999" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
<?php elseif (stripos($norm['description'] ?? '', '雾') !== false || stripos($norm['description'] ?? '', '霾') !== false): ?>
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2 12h20" stroke="#999" stroke-width="1.5" stroke-linecap="round"/><path d="M2 16h20" stroke="#999" stroke-width="1.5" stroke-linecap="round"/></svg>
<?php else: ?>
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="4" stroke="#ffb400" stroke-width="1.5"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" stroke="#ffb400" stroke-width="1.2" stroke-linecap="round"/></svg>
<?php endif; ?>
</div>
<div>
<p style="margin:0;font-weight:bold;line-height:1;font-size:1rem;"><?php echo htmlspecialchars($norm['city'] ?: $city, ENT_QUOTES); ?></p>
<p style="margin:0;line-height:1;color:#333;"><?php echo htmlspecialchars($norm['description'] ?? '-', ENT_QUOTES); ?> • <strong><?php echo htmlspecialchars($norm['temp'] ?? '-', ENT_QUOTES); ?>°C</strong></p>
</div>
</div>
<div style="width:1px"></div>
</div>
<div class="weather-details" style="font-size:0.85rem;color:#555;text-align:center;margin-top:4px;">
<p style="margin:0;">体感:<?php echo $m['feels_like']; ?>°C湿度<?php echo $m['humidity']; ?>%;气压:<?php echo $m['pressure']; ?> hPa</p>
<?php if (!empty($wind)): ?>
<p style="margin:0;">风速:<?php echo $wind['speed']; ?> m/s<?php
if (isset($wind['deg'])) echo ',方向:' . $wind['deg'] . '°'; ?></p>
<?php endif; ?>
<div class="weather-grid" style="display:grid;grid-template-columns:repeat(2,1fr);gap:6px;margin-top:8px;font-size:0.9rem;color:#444;">
<div style="display:flex;align-items:center;"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" style="margin-right:6px"><path d="M12 2v20" stroke="#ff6b6b" stroke-width="1.5" stroke-linecap="round"/></svg> 体感:<?php echo htmlspecialchars($norm['feels_like'] ?? '-', ENT_QUOTES); ?>°C</div>
<div style="display:flex;align-items:center;"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" style="margin-right:6px"><path d="M3 12h18" stroke="#5aa9ff" stroke-width="1.5" stroke-linecap="round"/></svg> 湿度:<?php echo htmlspecialchars($norm['humidity'] ?? '-', ENT_QUOTES); ?>%</div>
<div style="display:flex;align-items:center;"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" style="margin-right:6px"><path d="M3 6h18" stroke="#888" stroke-width="1.5" stroke-linecap="round"/></svg> 气压:<?php echo htmlspecialchars($norm['pressure'] ?? '-', ENT_QUOTES); ?> hPa</div>
<div style="display:flex;align-items:center;"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" style="margin-right:6px"><path d="M3 6l3 6h12l3-6" stroke="#6c6cff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg> 风:<?php echo htmlspecialchars($norm['wind_speed'] ?? '-', ENT_QUOTES); ?> m/s <?php echo htmlspecialchars($norm['wind_dir'] ?? '', ENT_QUOTES); ?><?php if (!empty($norm['wind_scale'])) echo ' 级别 ' . htmlspecialchars($norm['wind_scale'], ENT_QUOTES); ?></div>
<div style="display:flex;align-items:center;"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" style="margin-right:6px"><path d="M12 2v4" stroke="#2b7cff" stroke-width="1.5" stroke-linecap="round"/></svg> 降水:<?php echo htmlspecialchars($norm['precip'] ?? '-', ENT_QUOTES); ?> mm</div>
<div style="display:flex;align-items:center;"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" style="margin-right:6px"><path d="M3 12h18" stroke="#999" stroke-width="1.5" stroke-linecap="round"/></svg> 能见度:<?php echo htmlspecialchars($norm['vis'] ?? '-', ENT_QUOTES); ?> km</div>
<div style="display:flex;align-items:center;"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" style="margin-right:6px"><path d="M12 2c4 0 7 3 7 7s-3 7-7 7-7-3-7-7 3-7 7-7z" stroke="#999" stroke-width="1.2"/></svg> 云量:<?php echo htmlspecialchars($norm['cloud'] ?? '-', ENT_QUOTES); ?>%</div>
<div style="display:flex;align-items:center;"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" style="margin-right:6px"><path d="M3 12h18" stroke="#666" stroke-width="1.2"/></svg> 露点:<?php echo htmlspecialchars($norm['dew'] ?? '-', ENT_QUOTES); ?>°C</div>
</div>
<?php elseif ($weather && isset($weather['cod']) && $weather['cod'] != 200): ?>
<p>天气接口错误:<?php echo htmlspecialchars($weather['message'] ?? '未知', ENT_QUOTES); ?></p>
<?php if (!empty($norm['refer'])): ?>
<div style="font-size:0.65rem;color:#888;margin-top:6px;">来源:<?php echo htmlspecialchars(implode(', ', $norm['refer']['sources'] ?? $norm['refer'] ?? []), ENT_QUOTES); ?> &nbsp;•&nbsp; 更新时间:<?php echo htmlspecialchars($displayObsTime, ENT_QUOTES); ?></div>
<?php endif; ?>
<?php else: ?>
<p>天气获取失败</p>
<p>天气获取失败(请检查 API Key 与网络)</p>
<?php endif; ?>
</div>

View File

@@ -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(