172 lines
5.6 KiB
PHP
172 lines
5.6 KiB
PHP
<?php
|
|
// +----------------------------------------------------------------------
|
|
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
|
// +----------------------------------------------------------------------
|
|
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
|
// +----------------------------------------------------------------------
|
|
// | Author: ywxapp<admin@ywxapp.cn>
|
|
// +----------------------------------------------------------------------
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace addon\haonav\controller;
|
|
use ywxapp\controller\FrontendBase;
|
|
|
|
use think\facade\Cache;
|
|
use addon\haonav\model\Configure as ConfigureModel;
|
|
|
|
/**
|
|
* 首页小组件:实时热搜榜 + 天气(服务端代理 + 缓存,规避跨域并保护第三方接口)
|
|
*
|
|
* @author ywxapp <admin@ywxapp.cn>
|
|
*/
|
|
class Widget extends FrontendBase
|
|
{
|
|
protected $noNeedLogin = ['*'];
|
|
protected $noNeedVerify = ['*'];
|
|
|
|
protected function initialize()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* 实时热搜榜(缓存 10 分钟)
|
|
* GET /haonav/widget/hot?source=baidu|weibo|zhihu
|
|
*/
|
|
public function hot()
|
|
{
|
|
if ((int)ConfigureModel::getVal('enable_hot_api', '0') !== 1) {
|
|
return $this->result->success([], '未开启');
|
|
}
|
|
$source = $this->request->param('source', ConfigureModel::getVal('hot_api_source', 'baidu'));
|
|
$source = in_array($source, ['baidu', 'weibo', 'zhihu'], true) ? $source : 'baidu';
|
|
|
|
$cacheKey = 'haonav_hot_' . $source;
|
|
$data = Cache::get($cacheKey);
|
|
if (is_array($data)) {
|
|
return $this->result->success($data, '缓存');
|
|
}
|
|
|
|
$map = [
|
|
'baidu' => 'https://api.vvhan.com/api/hotlist/baiduRD',
|
|
'weibo' => 'https://api.vvhan.com/api/hotlist/wbHot',
|
|
'zhihu' => 'https://api.vvhan.com/api/hotlist/zhihuHot',
|
|
];
|
|
$list = $this->fetchHot($map[$source]);
|
|
// 即便为空也短缓存,避免频繁打第三方
|
|
Cache::set($cacheKey, $list, $list ? 600 : 120);
|
|
return $this->result->success($list);
|
|
}
|
|
|
|
/**
|
|
* 天气(缓存 30 分钟)
|
|
* GET /haonav/widget/weather?city=北京
|
|
*/
|
|
public function weather()
|
|
{
|
|
if ((int)ConfigureModel::getVal('enable_weather', '0') !== 1) {
|
|
return $this->result->success([], '未开启');
|
|
}
|
|
$city = trim((string)$this->request->param('city', ConfigureModel::getVal('weather_city', '')));
|
|
|
|
$cacheKey = 'haonav_weather_' . ($city !== '' ? md5($city) : 'auto');
|
|
$data = Cache::get($cacheKey);
|
|
if (is_array($data) && $data) {
|
|
return $this->result->success($data, '缓存');
|
|
}
|
|
|
|
$info = $this->fetchWeather($city);
|
|
if ($info) {
|
|
Cache::set($cacheKey, $info, 1800);
|
|
return $this->result->success($info);
|
|
}
|
|
return $this->result->success([], '暂无数据');
|
|
}
|
|
|
|
/**
|
|
* 拉取并归一化热搜列表 -> [ {title,url,hot}, ... ]
|
|
*/
|
|
protected function fetchHot(string $api): array
|
|
{
|
|
$json = $this->httpGet($api);
|
|
if (!$json) {
|
|
return [];
|
|
}
|
|
$arr = json_decode($json, true);
|
|
if (!is_array($arr)) {
|
|
return [];
|
|
}
|
|
$rows = $arr['data'] ?? ($arr['result'] ?? []);
|
|
if (!is_array($rows)) {
|
|
return [];
|
|
}
|
|
$out = [];
|
|
foreach ($rows as $r) {
|
|
if (!is_array($r)) {
|
|
continue;
|
|
}
|
|
$title = $r['title'] ?? ($r['name'] ?? '');
|
|
if ($title === '') {
|
|
continue;
|
|
}
|
|
$out[] = [
|
|
'title' => (string)$title,
|
|
'url' => (string)($r['url'] ?? ($r['mobil_url'] ?? '')),
|
|
'hot' => (string)($r['hot'] ?? ($r['num'] ?? '')),
|
|
];
|
|
if (count($out) >= 20) {
|
|
break;
|
|
}
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* 拉取并归一化天气 -> {city,type,low,high,tips,...}
|
|
*/
|
|
protected function fetchWeather(string $city)
|
|
{
|
|
$api = 'https://api.vvhan.com/api/weather';
|
|
if ($city !== '') {
|
|
$api .= '?city=' . urlencode($city);
|
|
}
|
|
$json = $this->httpGet($api);
|
|
if (!$json) {
|
|
return false;
|
|
}
|
|
$arr = json_decode($json, true);
|
|
if (!is_array($arr) || empty($arr['success'])) {
|
|
return false;
|
|
}
|
|
$today = $arr['data'][0] ?? [];
|
|
return [
|
|
'city' => (string)($arr['city'] ?? $city),
|
|
'date' => (string)($today['date'] ?? ''),
|
|
'type' => (string)($today['type'] ?? ''),
|
|
'low' => (string)($today['low'] ?? ''),
|
|
'high' => (string)($today['high'] ?? ''),
|
|
'fx' => (string)($today['fengxiang'] ?? ''),
|
|
'tips' => (string)($arr['tip'] ?? ''),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 轻量 GET(优先 Guzzle,失败回退 file_get_contents),失败返回 ''
|
|
*/
|
|
protected function httpGet(string $url): string
|
|
{
|
|
try {
|
|
if (class_exists(\GuzzleHttp\Client::class)) {
|
|
$client = new \GuzzleHttp\Client(['timeout' => 5, 'verify' => false]);
|
|
$resp = $client->get($url, ['headers' => ['Member-Agent' => 'Mozilla/5.0']]);
|
|
return (string)$resp->getBody();
|
|
}
|
|
$ctx = stream_context_create(['http' => ['timeout' => 5, 'header' => "Member-Agent: Mozilla/5.0\r\n"]]);
|
|
$body = @file_get_contents($url, false, $ctx);
|
|
return $body === false ? '' : (string)$body;
|
|
} catch (\Throwable $e) {
|
|
return '';
|
|
}
|
|
}
|
|
}
|