功能特性


实时天气显示(自动定位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-&gt;response-&gt;throwJson(array("error" =&gt; "未配置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-&gt;response-&gt;throwJson(json_decode($response, true));
} else {
    $self-&gt;response-&gt;throwJson(array("error" =&gt; "请求失败", "content" =&gt; "生活不止眼前的苟且,还有诗和远方。"));
}

}

/* 小渡API代理 - 获取IP定位 */
function _getDwoIp($self)
{
$self->response->setStatus(200);
$apiKey = Helper::options()->JDwoApiKey;
$ip = $self->request->ip;

if (empty($apiKey)) {
    return $self-&gt;response-&gt;throwJson(array("error" =&gt; "未配置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-&gt;response-&gt;throwJson(array(
        "success" =&gt; true,
        "city_name" =&gt; $cityName,
        "is_ipv6" =&gt; $isIPv6,
        "raw_data" =&gt; $data
    ));
} else {
    $self-&gt;response-&gt;throwJson(array("error" =&gt; "请求失败", "city_name" =&gt; "未知"));
}

}

/* 小渡API代理 - 获取天气 */
function _getDwoWeather($self)
{
$self->response->setStatus(200);
$apiKey = Helper::options()->JDwoApiKey;
$location = $self->request->location;

if (empty($apiKey)) {
    return $self-&gt;response-&gt;throwJson(array("error" =&gt; "未配置API Key"));
}

if (empty($location)) {
    return $self-&gt;response-&gt;throwJson(array("error" =&gt; "缺少城市参数"));
}

$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-&gt;response-&gt;throwJson(json_decode($response, true));
} else {
    $self-&gt;response-&gt;throwJson(array("error" =&gt; "请求失败"));
}

}

第三步:注册路由

  • 文件: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>

&lt;?php if ($this-&gt;options-&gt;JAside_Weather_Status == 'on') : ?&gt;
&lt;section class="joe_aside__item joe-dwo-weather"&gt;
  &lt;div class="joe_aside__item-title"&gt;
    &lt;svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="18" height="18"&gt;
      &lt;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" /&gt;
      &lt;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" /&gt;
    &lt;/svg&gt;
    &lt;span class="text"&gt;实时天气&lt;/span&gt;
    &lt;span class="line"&gt;&lt;/span&gt;
  &lt;/div&gt;
  &lt;div class="joe_aside__item-contain" id="dwo-weather-content"&gt;
    &lt;div class="loading"&gt;加载中...&lt;/div&gt;
  &lt;/div&gt;
&lt;/section&gt;
&lt;?php endif; ?&gt;

&lt;?php if ($this-&gt;options-&gt;JAside_Yiyan_Status == 'on') : ?&gt;
&lt;section class="joe_aside__item joe-dwo-yiyan"&gt;
  &lt;div class="joe_aside__item-title"&gt;
    &lt;svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="18" height="18"&gt;
      &lt;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" /&gt;
    &lt;/svg&gt;
    &lt;span class="text"&gt;每日一言&lt;/span&gt;
    &lt;span class="line"&gt;&lt;/span&gt;
  &lt;/div&gt;
  &lt;div class="joe_aside__item-contain" id="dwo-yiyan-content"&gt;
    &lt;div class="loading"&gt;加载中...&lt;/div&gt;
  &lt;/div&gt;
&lt;/section&gt;
&lt;?php endif; ?&gt;

&lt;script&gt;
(function() {
  const PROXY_API = '&lt;?php echo Helper::options()-&gt;index; ?&gt;/joe/api';
  
  function getClientIp() {
    return fetch('https://ipapi.co/json/').then(r =&gt; r.json());
  }
  
  &lt;?php if ($this-&gt;options-&gt;JAside_Weather_Status == 'on') : ?&gt;
  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 = `
          &lt;div class="weather-item"&gt;&lt;span&gt;城市&lt;/span&gt;${cityName}&lt;/div&gt;
          &lt;div class="weather-item"&gt;&lt;span&gt;天气&lt;/span&gt;${now.text}&lt;/div&gt;
          &lt;div class="weather-item"&gt;&lt;span&gt;温度&lt;/span&gt;${now.temperature}℃&lt;/div&gt;
        `;
      } else {
        dom.innerHTML = '&lt;div class="loading"&gt;暂无天气数据&lt;/div&gt;';
      }
    } catch (e) {
      console.error('天气加载失败:', e);
      dom.innerHTML = '&lt;div class="loading"&gt;加载失败&lt;/div&gt;';
    }
  }
  &lt;?php endif; ?&gt;
  
  &lt;?php if ($this-&gt;options-&gt;JAside_Yiyan_Status == 'on') : ?&gt;
  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 = '&lt;div class="yiyan-content"&gt;' + result.data.content + '&lt;/div&gt;';
      } else if (result.content) {
        dom.innerHTML = '&lt;div class="yiyan-content"&gt;' + result.content + '&lt;/div&gt;';
      } else {
        dom.innerHTML = '&lt;div class="yiyan-content"&gt;生活不止眼前的苟且,还有诗和远方。&lt;/div&gt;';
      }
    } catch (e) {
      console.error('一言加载失败:', e);
      dom.innerHTML = '&lt;div class="yiyan-content"&gt;生活不止眼前的苟且,还有诗和远方。&lt;/div&gt;';
    }
  }
  &lt;?php endif; ?&gt;
  
  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();
      &lt;?php if ($this-&gt;options-&gt;JAside_Weather_Status == 'on') : ?&gt;loadWeather();&lt;?php endif; ?&gt;
      &lt;?php if ($this-&gt;options-&gt;JAside_Yiyan_Status == 'on') : ?&gt;loadYiyan();&lt;?php endif; ?&gt;
    });
  } else {
    loadMottoYiyan();
    &lt;?php if ($this-&gt;options-&gt;JAside_Weather_Status == 'on') : ?&gt;loadWeather();&lt;?php endif; ?&gt;
    &lt;?php if ($this-&gt;options-&gt;JAside_Yiyan_Status == 'on') : ?&gt;loadYiyan();&lt;?php endif; ?&gt;
  }
})();
&lt;/script&gt;

<?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修改替换天气 / 一言显示代码
好了就这点文字您慢慢看