功能特性
实时天气显示(自动定位IP城市)
每日一言(侧边栏)
博主栏座右铭动态一言
API Key 后端代理隐藏(防泄漏)
只需配置一个 Key,三功能通用
---
第一步:修改后台配置
- 文件:functions.php
- 位置:第 569-596 行(侧栏设置区域)
- 操作:替换原有的天气配置为新的通用配置
/* --------------------------------------- */
$JDwoApiKey = new Typecho_Widget_Helper_Form_Element_Text(
'JDwoApiKey',
NULL,
NULL,
'小渡API Key - 通用',
'介绍:用于天气栏和一言功能的小渡API密钥 <br/>
注意:填写时务必填写正确!不填写则不会显示天气和一言<br />
免费申请地址:<a href="https://openapi.dwo.cc/" target="_blank">openapi.dwo.cc</a><br />
说明:此Key同时用于天气查询、一言接口和博主栏座右铭,只需要配置这一个即可'
);
$JDwoApiKey->setAttribute('class', 'joe_content joe_aside');
$form->addInput($JDwoApiKey);
/* --------------------------------------- */
$JAside_Weather_Status = new Typecho_Widget_Helper_Form_Element_Select(
'JAside_Weather_Status',
array(
'off' => '关闭(默认)',
'on' => '开启'
),
'off',
'是否开启实时天气栏 - PC',
'介绍:用于控制是否显示实时天气栏 <br />
注意:需要先填写上方的小渡API Key才会生效'
);
$JAside_Weather_Status->setAttribute('class', 'joe_content joe_aside');
$form->addInput($JAside_Weather_Status->multiMode());
/* --------------------------------------- */
$JAside_Yiyan_Status = new Typecho_Widget_Helper_Form_Element_Select(
'JAside_Yiyan_Status',
array(
'off' => '关闭(默认)',
'on' => '开启'
),
'off',
'是否开启一言栏 - PC',
'介绍:用于控制是否显示一言(每日一句)栏 <br />
注意:需要先填写上方的小渡API Key才会生效'
);
$JAside_Yiyan_Status->setAttribute('class', 'joe_content joe_aside');
$form->addInput($JAside_Yiyan_Status->multiMode());- 同时修改博主栏座右铭说明(第 488-500 行):
$JAside_Author_Motto = new Typecho_Widget_Helper_Form_Element_Textarea(
'JAside_Author_Motto',
NULL,
"有钱终成眷属,没钱亲眼目睹",
'博主栏座右铭(一言)- PC/WAP',
'介绍:用于修改博主栏的座右铭(一言) <br />
注意:如果配置了上方的小渡API Key,此项将失效,自动使用小渡API获取动态一言 <br />
格式:可以填写多行也可以填写一行,填写多行时,每次随机显示其中的某一条 <br />
其他:未配置小渡API Key时,将显示此处设置的内容'
);第二步:添加后端代理接口
- 文件:core/route.php
- 位置:文件末尾(第 413 行后)
- 操作:添加以下 3 个代理函数
/* 小渡API代理 - 获取一言 */
function _getDwoYiyan($self)
{
$self->response->setStatus(200);
$apiKey = Helper::options()->JDwoApiKey;
if (empty($apiKey)) {
return $self->response->throwJson(array("error" => "未配置API Key"));
}
$url = "https://openapi.dwo.cc/api/yi?type=json&ckey=" . urlencode($apiKey);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode == 200 && $response) {
$self->response->throwJson(json_decode($response, true));
} else {
$self->response->throwJson(array("error" => "请求失败", "content" => "生活不止眼前的苟且,还有诗和远方。"));
}
}
/* 小渡API代理 - 获取IP定位 */
function _getDwoIp($self)
{
$self->response->setStatus(200);
$apiKey = Helper::options()->JDwoApiKey;
$ip = $self->request->ip;
if (empty($apiKey)) {
return $self->response->throwJson(array("error" => "未配置API Key"));
}
// 判断IPv6
$isIPv6 = strpos($ip, ':') !== false;
$apiUrl = $isIPv6 ?
"https://openapi.dwo.cc/api/ipv6?ip=" . urlencode($ip) . "&ckey=" . urlencode($apiKey) :
"https://openapi.dwo.cc/api/ip?ip=" . urlencode($ip) . "&ckey=" . urlencode($apiKey);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $apiUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode == 200 && $response) {
$data = json_decode($response, true);
// 统一返回格式:提取城市名称(处理IPv4和IPv6不同格式)
$cityName = '未知';
if ($isIPv6) {
// IPv6 格式:data.city
if (!empty($data['data']['city'])) {
$cityName = $data['data']['city'];
}
} else {
// IPv4 格式:data.data.city_name
if (!empty($data['data']['data']['city_name'])) {
$cityName = $data['data']['data']['city_name'];
}
}
// 移除"市"后缀,天气API可能不需要
$cityName = str_replace(['市', '县', '区'], '', $cityName);
$self->response->throwJson(array(
"success" => true,
"city_name" => $cityName,
"is_ipv6" => $isIPv6,
"raw_data" => $data
));
} else {
$self->response->throwJson(array("error" => "请求失败", "city_name" => "未知"));
}
}
/* 小渡API代理 - 获取天气 */
function _getDwoWeather($self)
{
$self->response->setStatus(200);
$apiKey = Helper::options()->JDwoApiKey;
$location = $self->request->location;
if (empty($apiKey)) {
return $self->response->throwJson(array("error" => "未配置API Key"));
}
if (empty($location)) {
return $self->response->throwJson(array("error" => "缺少城市参数"));
}
$url = "https://openapi.dwo.cc/api/weather_xz?location=" . urlencode($location) . "&language=zh-Hans&unit=c&ckey=" . urlencode($apiKey);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode == 200 && $response) {
$self->response->throwJson(json_decode($response, true));
} else {
$self->response->throwJson(array("error" => "请求失败"));
}
}
第三步:注册路由
- 文件:core/core.php
- 位置:第 69-72 行(在 article_filing case 后面)
- 操作:添加路由注册
case 'article_filing':
_getArticleFiling($self);
break;
case 'dwo_yiyan':
_getDwoYiyan($self);
break;
case 'dwo_ip':
_getDwoIp($self);
break;
case 'dwo_weather':
_getDwoWeather($self);
break;
};第四步:修改侧边栏显示
文件:public/aside.php
4.1 修改博主栏座右铭部分(第 7 行)
- 替换为:
<p class="motto joe_motto">
<?php if (!empty($this->options->JDwoApiKey)) : ?>
<span class="dwo-motto">加载中...</span>
<?php else : ?>
<?php echo $this->options->JAside_Author_Motto ? $this->options->JAside_Author_Motto() : '有钱终成眷属,没钱亲眼目睹'; ?>
<?php endif; ?>
</p>4.2 替换天气模块(第 181-195 行)
- 替换为:
<?php if (!empty($this->options->JDwoApiKey)) : ?>
<style>
.joe-dwo-weather, .joe-dwo-yiyan {
background: var(--background) !important;
backdrop-filter: none !important;
}
.joe-dwo-weather .weather-item {
display: flex;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid var(--classA);
font-size: 14px;
color: var(--theme);
}
.joe-dwo-weather .weather-item:last-child {
border-bottom: none;
}
.joe-dwo-weather .weather-item span {
color: var(--minor);
font-weight: 500;
}
.joe-dwo-weather .loading, .joe-dwo-yiyan .loading {
color: var(--minor);
font-size: 14px;
text-align: center;
padding: 15px 0;
}
.joe-dwo-yiyan .yiyan-content {
font-size: 14px;
line-height: 1.8;
text-align: center;
padding: 15px 10px;
font-style: italic;
color: var(--theme);
font-weight: 500;
}
</style>
<?php if ($this->options->JAside_Weather_Status == 'on') : ?>
<section class="joe_aside__item joe-dwo-weather">
<div class="joe_aside__item-title">
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="18" height="18">
<path d="M773.12 757.76h-79.872c-15.36 0-29.696-15.36-29.696-29.696s15.36-29.696 29.696-29.696h79.872c100.352 0 180.224-79.872 180.224-180.224S873.472 337.92 773.12 337.92c-25.6 0-50.176 5.12-74.752 15.36-10.24 5.12-20.48 5.12-25.6 0-10.24-5.12-15.36-15.36-15.36-20.48-15.36-100.352-100.352-175.104-200.704-175.104C346.112 155.648 256 245.76 250.88 356.352c0 15.36-10.24 29.696-29.696 29.696-79.872 5.12-145.408 74.752-145.408 160.768 0 90.112 70.656 160.768 160.768 160.768h75.776c15.36 0 29.696 15.36 29.696 29.696S326.656 768 311.296 768h-79.872C110.592 757.76 10.24 662.528 10.24 541.696c0-105.472 75.776-195.584 175.104-216.064 15.36-130.048 130.048-235.52 266.24-235.52 120.832 0 225.28 79.872 256 195.584 20.48-5.12 45.056-10.24 65.536-10.24 135.168 1.024 240.64 111.616 240.64 241.664S903.168 757.76 773.12 757.76z" />
<path d="M437.248 933.888c-10.24 0-15.36-5.12-20.48-10.24-10.24-10.24-10.24-29.696 0-45.056l79.872-79.872h-60.416c-10.24 0-25.6-5.12-29.696-20.48-5.12-10.24 0-24.576 5.12-34.816l130.048-130.048c10.24-10.24 29.696-10.24 45.056 0 10.24 10.24 10.24 29.696 0 45.056L512 742.4h55.296c10.24 0 24.576 5.12 29.696 20.48 5.12 10.24 0 24.576-5.12 34.816L461.824 928.768c-10.24 5.12-20.48 5.12-24.576 5.12z" />
</svg>
<span class="text">实时天气</span>
<span class="line"></span>
</div>
<div class="joe_aside__item-contain" id="dwo-weather-content">
<div class="loading">加载中...</div>
</div>
</section>
<?php endif; ?>
<?php if ($this->options->JAside_Yiyan_Status == 'on') : ?>
<section class="joe_aside__item joe-dwo-yiyan">
<div class="joe_aside__item-title">
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="18" height="18">
<path d="M512 938.667A426.667 426.667 0 0 1 85.333 512a421.12 421.12 0 0 1 131.2-306.133 58.88 58.88 0 0 1 42.667-16.64c33.28 1.066 58.027 28.16 84.267 56.96 7.893 8.533 19.626 21.333 28.373 29.013a542.933 542.933 0 0 0 24.533-61.867c18.134-52.266 35.414-101.76 75.307-121.6 55.04-27.733 111.573 37.974 183.253 121.6 16.214 18.774 38.614 44.8 53.547 59.52 1.707-4.48 3.2-8.96 4.48-12.373 8.533-24.32 18.987-54.613 51.2-61.653a57.813 57.813 0 0 1 55.68 20.053A426.667 426.667 0 0 1 512 938.667zM260.693 282.453A336.64 336.64 0 0 0 170.667 512a341.333 341.333 0 1 0 614.826-203.733 90.24 90.24 0 0 1-42.666 50.56 68.267 68.267 0 0 1-53.547 1.706c-25.6-9.173-51.627-38.4-99.2-93.226a826.667 826.667 0 0 0-87.253-91.734 507.733 507.733 0 0 0-26.24 64c-18.134 52.267-35.414 101.76-75.947 119.254-48.853 21.333-88.32-21.334-120.107-56.96-5.76-4.694-13.226-13.014-19.84-19.414z" />
</svg>
<span class="text">每日一言</span>
<span class="line"></span>
</div>
<div class="joe_aside__item-contain" id="dwo-yiyan-content">
<div class="loading">加载中...</div>
</div>
</section>
<?php endif; ?>
<script>
(function() {
const PROXY_API = '<?php echo Helper::options()->index; ?>/joe/api';
function getClientIp() {
return fetch('https://ipapi.co/json/').then(r => r.json());
}
<?php if ($this->options->JAside_Weather_Status == 'on') : ?>
async function loadWeather() {
const dom = document.getElementById('dwo-weather-content');
if (!dom) return;
try {
const ipData = await getClientIp();
const clientIp = ipData.ip;
const locRes = await fetch(PROXY_API + '?routeType=dwo_ip&ip=' + encodeURIComponent(clientIp));
const locData = await locRes.json();
const cityName = locData.city_name ? locData.city_name : '未知';
const weatherRes = await fetch(PROXY_API + '?routeType=dwo_weather&location=' + encodeURIComponent(cityName));
const result = await weatherRes.json();
if (result.results && result.results[0] && result.results[0].now) {
const now = result.results[0].now;
dom.innerHTML = `
<div class="weather-item"><span>城市</span>${cityName}</div>
<div class="weather-item"><span>天气</span>${now.text}</div>
<div class="weather-item"><span>温度</span>${now.temperature}℃</div>
`;
} else {
dom.innerHTML = '<div class="loading">暂无天气数据</div>';
}
} catch (e) {
console.error('天气加载失败:', e);
dom.innerHTML = '<div class="loading">加载失败</div>';
}
}
<?php endif; ?>
<?php if ($this->options->JAside_Yiyan_Status == 'on') : ?>
async function loadYiyan() {
const dom = document.getElementById('dwo-yiyan-content');
if (!dom) return;
try {
const res = await fetch(PROXY_API + '?routeType=dwo_yiyan');
const result = await res.json();
if (result.data && result.data.content) {
dom.innerHTML = '<div class="yiyan-content">' + result.data.content + '</div>';
} else if (result.content) {
dom.innerHTML = '<div class="yiyan-content">' + result.content + '</div>';
} else {
dom.innerHTML = '<div class="yiyan-content">生活不止眼前的苟且,还有诗和远方。</div>';
}
} catch (e) {
console.error('一言加载失败:', e);
dom.innerHTML = '<div class="yiyan-content">生活不止眼前的苟且,还有诗和远方。</div>';
}
}
<?php endif; ?>
async function loadMottoYiyan() {
const mottoDom = document.querySelector('.dwo-motto');
if (!mottoDom) return;
try {
const res = await fetch(PROXY_API + '?routeType=dwo_yiyan');
const result = await res.json();
if (result.data && result.data.content) {
mottoDom.textContent = result.data.content;
} else if (result.content) {
mottoDom.textContent = result.content;
} else {
mottoDom.textContent = '生活不止眼前的苟且,还有诗和远方。';
}
} catch (e) {
console.error('座右铭一言加载失败:', e);
mottoDom.textContent = '生活不止眼前的苟且,还有诗和远方。';
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
loadMottoYiyan();
<?php if ($this->options->JAside_Weather_Status == 'on') : ?>loadWeather();<?php endif; ?>
<?php if ($this->options->JAside_Yiyan_Status == 'on') : ?>loadYiyan();<?php endif; ?>
});
} else {
loadMottoYiyan();
<?php if ($this->options->JAside_Weather_Status == 'on') : ?>loadWeather();<?php endif; ?>
<?php if ($this->options->JAside_Yiyan_Status == 'on') : ?>loadYiyan();<?php endif; ?>
}
})();
</script>
<?php endif; ?>
第五步:使用方法
1. 申请小渡 API Key
访问 https://api.dwo.cc/注册账号并申请免费 API Key
2. 后台配置
- 进入 Typecho 后台 → 控制台 → 外观设置 → 侧栏设置
- 填写「小渡 API Key - 通用」
- 开启「实时天气栏」和 / 或「一言栏」
- 保存设置
3. 查看效果
- 刷新博客页面侧边栏会显示实时天气和每日一言
- 博主栏座右铭自动显示动态一言API
- Key 完全隐藏,不会出现在前端请求中
文件修改清单
| 文件路径 | 修改类型 | 说明 |
|---|---|---|
| functions.php | 修改 | 替换天气配置为通用小渡 API 配置 |
| core/route.php | 新增 | 添加 3 个代理接口函数 |
| core/core.php | 新增 | 注册代理路由 |
| public/aside.php | 修改 | 替换天气 / 一言显示代码 |
好了就这点文字您慢慢看



