效果:网页版的朋友圈
1、默认缓存1小时
2、管理员状态可点击 “刷新缓存” 按钮更新。
3、确保usr/cache/目录权限为755,文件权限为644,否则缓存失效,页面加载慢。
4、提取了每个订阅的最新2篇文章
5、增加了重试机制配置。
最大重试次数($maxRetries = 3)
设置了重试延迟时间($retryDelay = 1 秒)
最终失败时会在错误信息中显示尝试次数
<?php
/**
* 朋友圈
*
* @package custom
*/
if (!defined('__TYPECHO_ROOT_DIR__')) exit;?>
<?php $this->need('component/header.php'); ?>
<!-- 引入pyq.css样式文件 -->
<link rel="stylesheet" href="<?php $this->options->themeUrl('assets/css/pyq.css'); ?>">
<!-- aside -->
<?php $this->need('component/aside.php'); ?>
<!-- / aside -->
<a class="off-screen-toggle hide"></a>
<main class="app-content-body <?php echo Content::returnPageAnimateClass($this); ?>">
<div class="hbox hbox-auto-xs hbox-auto-sm">
<!--文章-->
<div class="col center-part gpu-speed" id="post-panel">
<?php echo Content::exportPostPageHeader($this,$this->user->uid); ?>
<div class="wrapper-md">
<?php echo Content::BreadcrumbNavigation($this, $this->options->rootUrl); ?>
<div id="postpage" class="blog-post">
<article class="single-post panel">
<?php echo Content::exportHeaderImg($this); ?>
<div id="post-content" class="wrapper-lg">
<div class="container">
<h1><?php $this->title() ?></h1>
<?php
// 缓存设置
$cacheDir = __TYPECHO_ROOT_DIR__ . '/usr/cache/'; // Typecho默认缓存目录
$cacheFile = $cacheDir . 'friends_circle_cache.html';
$cacheTime = 3600; // 缓存时间:1小时(3600秒)
// 确保缓存目录存在
if (!is_dir($cacheDir)) {
mkdir($cacheDir, 0755, true);
}
// 尝试读取缓存
$cachedContent = false;
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $cacheTime)) {
$cachedContent = file_get_contents($cacheFile);
}
// 管理员判断(Typecho 权限控制)
if ($this->user->hasLogin()) {
if (isset($_GET['refresh_pyq_cache']) && $_GET['refresh_pyq_cache'] == '1') {
// 删除缓存文件
if (file_exists($cacheFile)) unlink($cacheFile);
// 刷新页面
$this->response->redirect($this->permalink);
}
// 输出刷新按钮
echo '<a href="' . $this->permalink . '?refresh_pyq_cache=1" class="refresh-cache-btn">刷新内容</a>';
}
if ($cachedContent === false) {
// 缓存不存在或已过期,重新获取内容
$feedUrls = [
'李的博客' => 'https://lilog.cn/feed',
'刘郎阁' => 'https://vjo.cc/feed'
];
$allArticles = [];
$errorMessages = [];
// 创建超时上下文(5秒超时)
$context = stream_context_create([
'http' => [
'timeout' => 5, // 超时时间设置为5秒
'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' // 添加用户代理,避免部分服务器拒绝访问
],
'https' => [
'timeout' => 5 // HTTPS链接同样应用5秒超时
]
]);
foreach ($feedUrls as $blogName => $feedUrl) {
// 解析RSS(添加超时控制)
$feedContent = @file_get_contents($feedUrl, false, $context);
// 修改为带重试机制的代码
$maxRetries = 3; // 最大重试次数
$retryDelay = 1000000; // 重试延迟(微秒),1秒=1000000微秒
$feedContent = false;
// 重试循环
for ($retry = 0; $retry < $maxRetries; $retry++) {
$feedContent = @file_get_contents($feedUrl, false, $context);
if ($feedContent !== false) {
break; // 获取成功,跳出重试循环
}
// 重试前等待一段时间
if ($retry < $maxRetries - 1) {
usleep($retryDelay);
}
}
// 所有重试都失败
if (!$feedContent) {
$errorMessages[] = "无法获取 {$blogName} 的内容(经过 {$maxRetries} 次重试仍失败)";
continue;
}
// 加载XML
$xml = simplexml_load_string($feedContent);
if (!$xml) {
$errorMessages[] = "{$blogName} 的RSS格式错误";
continue;
}
// 解析RSS(兼容RSS 2.0和Atom)
$items = [];
if (isset($xml->channel->item)) {
// RSS 2.0
$items = $xml->channel->item;
} elseif (isset($xml->entry)) {
// Atom
$items = $xml->entry;
}
if (empty($items)) {
$errorMessages[] = "{$blogName} 暂无内容";
continue;
}
// 提取最新2篇文章
$count = 0;
foreach ($items as $item) {
if ($count >= 2) break;
// 获取标题和链接
$title = (string)($item->title ?? '无标题');
$link = (string)($item->link ?? ($item->guid ?? '#'));
// 处理Atom格式的链接
if (isset($item->link['href'])) {
$link = (string)$item->link['href'];
}
// 获取发布时间
$pubDate = (string)($item->pubDate ?? ($item->published ?? ''));
$pubTime = strtotime($pubDate);
$displayDate = $pubDate ? date('Y-m-d', $pubTime) : '';
// 将文章添加到总数组
$allArticles[] = [
'title' => $title,
'link' => $link,
'date' => $displayDate,
'timestamp' => $pubTime,
'source' => $blogName
];
$count++;
}
}
// 按发布时间排序(最新的在前)
usort($allArticles, function($a, $b) {
return $b['timestamp'] - $a['timestamp'];
});
// 生成输出内容
$output = '';
// 输出错误信息
if (!empty($errorMessages)) {
foreach ($errorMessages as $msg) {
$output .= "<div class='error-message'>{$msg}</div>";
}
}
// 输出文章卡片
if (!empty($allArticles)) {
$output .= '<div class="articles-container">';
foreach ($allArticles as $article) {
$output .= '<div class="article-card">';
$output .= '<div class="card-title"><a href="' . $article['link'] . '" target="_blank" rel="noopener">' . $article['title'] . '</a></div>';
$output .= '<div class="card-meta">';
if ($article['date']) {
$output .= '<span class="card-date">' . $article['date'] . '</span>';
}
$output .= '<span class="card-source">来源: ' . $article['source'] . '</span>';
$output .= '</div></div>';
}
$output .= '</div>';
} else {
$output .= '<div class="error-message">没有获取到任何文章内容</div>';
}
// 保存到缓存文件
file_put_contents($cacheFile, $output);
$cachedContent = $output;
}
// 输出最终内容
echo $cachedContent;
?>
</div>
<?php Content::pageFooter($this->options,$this) ?>
</div>
</article>
</div>
<?php $this->need('component/comments.php') ?>
</div>
<?php echo WidgetContent::returnRightTriggerHtml() ?>
</div>
<!--文章右侧边栏开始-->
<?php $this->need('component/sidebar.php'); ?>
<!--文章右侧边栏结束-->
</div>
</main>
<?php echo Content::returnReadModeContent($this,$this->user->uid,$content); ?>
<!-- footer -->
<?php $this->need('component/footer.php'); ?>
<!-- / footer -->
/* 文章卡片样式 */
.article-card {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
border: 1px solid #f0f0f0;
padding: 12px;
box-sizing: border-box;
}
.article-card:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
/* 文章标题 - 移除h3标签,使用div+class,字号16px */
.card-title {
margin: 0 0 10px 0;
font-size: 16px; /* 标题字号16px */
line-height: 1.4;
font-weight: normal;
}
.card-title a {
color: #2d3748;
text-decoration: none;
transition: color 0.2s;
}
.card-title a:hover {
color: #3182ce;
text-decoration: none;
}
/* 元数据区域(日期和来源)- 同一行显示 */
.card-meta {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
gap: 8px; /* 两者之间保留间距 */
flex-wrap: wrap; /* 防止小屏幕内容重叠 */
}
/* 发布日期和来源共享样式 */
.card-date, .card-source {
color: #718096;
background: #f7fafc; /* 来源添加与日期相同的背景 */
padding: 2px 6px;
border-radius: 3px;
white-space: nowrap; /* 防止内容换行 */
}
/* 错误提示样式 */
.error-message {
background: #fff5f5;
border-left: 4px solid #e53e3e;
padding: 15px;
margin: 10px 0;
color: #c53030;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
}
本文著作权归作者 [ 老罗 ] 享有,未经作者书面授权,禁止转载,封面图片来源于 [ 互联网 ] ,本文仅供个人学习、研究和欣赏使用。如有异议,请联系博主及时处理。
可增加重试机制避免网络波动异常
好的,我去试试
呜,为啥抓不到我的 ::QQ:ganga::
换你atom那个抓到了😄
什么叫换,我好像本来就只有这一个来着:$(闭嘴)
你忘记了吧😂,我在你这个订阅页面https://blog.liushen.fun/subscribe看到的,有两个。
RSS2 格式:https://blog.liushen.fun/rss2.xml
Atom 格式:https://blog.liushen.fun/atom.xml
不小心被我发现碰巧又用上了😄
嘶,老早之前删掉了,没想到这里还写着哈哈:$(坏笑)
自己之前弄了好几个rss订阅的 感觉都用不上 不好用😂后面直接放弃了
我也是不断捣鼓这个😄
还有获取不到的提示语,不错(可以把没开通feed规避掉
下一步打算把订阅源放在后台主题设置里添加,这个主题有点复杂
效果不错。有的链接没有更新
有些可能是临时打不开