本来我很少会写建站相关的博文,因为这些在网上一翻一大堆,写了也是重复造轮子。
不过昨天有个博主问到了本站的“朋友圈”是如何实现的,正巧这个“朋友圈”的创建并非是完全参考某一个教程搭建的,中间也有不少我的魔改,所以就记录一下。
PS1:本站“朋友圈”其实是对订阅的其他网站的RSS的内容的一个集合展示。
PS2:实现原理:
- 部署 FreshRSS 用于订阅其他博主/网站的 RSS 订阅源;
- 使用宝塔做了两个定时任务:
- 一个定时任务让 FreshRSS 刷新订阅源的最新内容;
- 另一个定时任务是将最新的前 n 条内容覆写到一个指定的 json 文件中。
- 在网站后台(WordPress)魔改了说说模块文件,其目的是读取的这个 json 内容并展示;
- 最后就形成了我现在的 “朋友圈” 的效果。
PS3:博主不会php语言,所以下面的魔改部分都是我摸索着配出来的,本着能用就行的态度写的,所以会有很多写的不对的地方,多多包涵,欢迎指正。
一、配置清单
下面是我的配置清单,我只能保证最终效果在本配置下生效。
- 服务器系统: CentOS 7.9
- 博客平台: WordPress 6.8.0及之后
- 主题模板: Argon 1.3.5
- 配置托管平台: 宝塔Linux面板(没有配置托管平台的,其过程也可以手搓)
- RSS订阅工具: Fresh RSS
二、安装配置FreshRSS
点击跳转☞:【FreshRSS官网】
点击跳转☞:【FreshRSS官方安装文档】
点击跳转☞:【FreshRSS官方下载页面(发文时版本)】
2.1 下载FreshRSS
打开下载页面,拉到最下面,根据自己的需要下载zip或者tar.gz版本。
2.2 部署FreshRSS
- 将下载的压缩包上传到centos的某目录下并解压;
- 然后使用宝塔,点击“网站” > “添加站点”;
- “域名”:可以提前对主域名进行解析出一个二级域名,可以加SSL证书做成HTTP的;
- “根目录”:选择刚才解压的目录;
- 其他的默认即可。
- 创建好站点之后,点击站点后的“配置”,你可以根据需要进行后续配置,比如配置SSL证书;
- 默认使用80和443端口,记得开启防火墙,做好二级域名的DNS解析。这样的话,你就可以通过前面的二级域名访问到自己搭建的FreshRSS网站了。
2.3 添加RSS订阅源
根据需要添加“分类”,或者添加“订阅源”,此处的订阅源就是其他站点的RSS地址,订阅源的可选择分类就是上面自己添加的分类。
添加完订阅源之后,返回首页刷新就会加载最新的订阅源的内容。
到这一步,你就成功的能在FreshRSS看到其他站点的文章了。
但是FreshRSS有一个问题,就是不能主动刷新订阅源,需要我们手动点击页面的【⟳】按钮才能刷新。
我们要的是定时自动刷新,所以我们还需要做相关配置,详情见下面第六节。
2.4 配置FreshRSS
打开你的FreshRSS的域名,并进行登陆。
点击 设置 > 管理 > 认证;
- 勾选“允许 API 访问”并提交。
- 勾选“允许 API 访问”并提交。
点击 设置 > 账户 > API管理;
- 设置密码并提交保存,记住设置的api密码。
- 复制网址,将在下一步配置文件中用于
${网址1}
:
- 设置密码并提交保存,记住设置的api密码。
在自己站点(WordPress)根目录下创建一个php文件,用于放FreshRSS api调用函数,例如:rss.php。
例如:- 我的博客网站根目录为:
/www/wwwroot/www.tqazy.com
- 我的FreshRSS根目录为:
/www/wwwroot/rss.tqazy.com
那么我就在/www/wwwroot/www.tqazy.com
下创建创建一个文件:rss.php,内容如下:
- 我的博客网站根目录为:
<?php
/**
* 获取最新订阅文章并生成JSON文件
*/
function getAllSubscribedArticlesAndSaveToJson($user, $password)
{
$apiUrl = '${网址1}';
$loginUrl = $apiUrl . '/accounts/ClientLogin?Email=' . urlencode($user) . '&Passwd=' . urlencode($password);
$loginResponse = curlRequest($loginUrl);
// 处理可能的cURL错误
if (isset($loginResponse['error'])) {
die('登录请求失败: ' . $loginResponse['error']);
}
if (strpos($loginResponse, 'Auth=') !== false) {
$authToken = substr($loginResponse, strpos($loginResponse, 'Auth=') + 5);
$articlesUrl = $apiUrl . '/reader/api/0/stream/contents/reading-list?&n=1000';
$articlesResponse = curlRequest($articlesUrl, $authToken);
// 处理可能的cURL错误
if (isset($articlesResponse['error'])) {
die('获取文章失败: ' . $articlesResponse['error']);
}
$articles = json_decode($articlesResponse, true);
if (isset($articles['items'])) {
usort($articles['items'], function ($a, $b) {
return $b['published'] - $a['published'];
});
$subscriptionsUrl = $apiUrl . '/reader/api/0/subscription/list?output=json';
$subscriptionsResponse = curlRequest($subscriptionsUrl, $authToken);
// 处理可能的cURL错误
if (isset($subscriptionsResponse['error'])) {
die('获取订阅失败: ' . $subscriptionsResponse['error']);
}
$subscriptions = json_decode($subscriptionsResponse, true);
if (isset($subscriptions['subscriptions'])) {
$subscriptionMap = array();
foreach ($subscriptions['subscriptions'] as $subscription) {
$subscriptionMap[$subscription['id']] = $subscription;
}
$formattedArticles = array();
foreach ($articles['items'] as $article) {
// 去掉以 http 或 https 开头的链接
$contentWithoutLinks = preg_replace('/https?:\/\/[^\s?#]*?\.(jpg|jpeg|png|gif|mp4|mp3|webm|ogg|wav|flac|svg|bmp)\b/i', '', $article['summary']['content']);
$desc_length = mb_strlen(strip_tags(html_entity_decode($contentWithoutLinks, ENT_QUOTES, 'UTF-8')), 'UTF-8');
if ($desc_length > 20) {
$short_desc = mb_substr(strip_tags(html_entity_decode($contentWithoutLinks, ENT_QUOTES, 'UTF-8')), 0, 99, 'UTF-8') . '...';
} else {
$short_desc = strip_tags(html_entity_decode($contentWithoutLinks, ENT_QUOTES, 'UTF-8'));
}
$formattedArticle = array(
'site_name' => $article['origin']['title'],
'title' => $article['title'],
'link' => $article['alternate'][0]['href'],
'time' => date('Y-m-d H:i', $article['published']),
'description' => $short_desc,
);
$subscriptionId = $article['origin']['streamId'];
if (isset($subscriptionMap[$subscriptionId])) {
$subscription = $subscriptionMap[$subscriptionId];
$iconUrl = $subscription['iconUrl'];
$filename = '${网址2}/' . substr($iconUrl, strrpos($iconUrl, '/') + 1);
$formattedArticle['icon'] = $iconUrl;
}
$formattedArticles[] = $formattedArticle;
}
saveToJsonFile($formattedArticles);
return $formattedArticles;
} else {
die('Error retrieving subscriptions.');
}
} else {
die('Error retrieving articles.');
}
} else {
die('Login failed: ' . $loginResponse);
}
return null;
}
function curlRequest($url, $authToken = null)
{
$ch = curl_init($url);
if ($ch === false) {
return ['error' => 'cURL初始化失败'];
}
$headers = [];
if ($authToken) {
$headers[] = 'Authorization: GoogleLogin auth=' . $authToken;
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FAILONERROR, true); // 捕获HTTP错误
$response = curl_exec($ch);
if ($response === false) {
$error = curl_error($ch);
curl_close($ch);
return ['error' => $error];
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 400) {
return ['error' => "HTTP请求失败,状态码:$httpCode"];
}
return $response;
}
/**
* 将数据保存到JSON文件中
*/
function saveToJsonFile($data)
{
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
file_put_contents('friends_rss.json', $json);
echo '数据已保存到JSON文件中';
}
// 调用函数并提供用户名和密码
getAllSubscribedArticlesAndSaveToJson('${账户}', '${密码}');
代码中的替换项:
${网址1}
:上一步复制的网址;${网址2}
:替换成你FreshRSS的域名地址,比如:https://rss.xxx.com;${账户}
:FreshRSS 的账户${密码}
:这里是第3步设置的api密码
调用这个rss.php文件,就能从FreshRSS中把最新的订阅文章复写到一个叫friends_rss.json的文件中。
三、配置“朋友圈”模板文件
我们拿到了需要展示的数据,那么我们就要有一个展示的模板。
我的朋友圈设计是仅展示订阅文章的“文章名”、“发布时间”、“发布网站”、“文章前n个字符”等,并不会显示全文,如图:
这个模板其实是由WordPress的说说魔改的,去掉了翻页(仅展示前n条最新文章)、去掉了评论、去掉了点赞、新增了作者栏、修改了标题名自动加书名号、修改了内容超出部分自动加…等。
在目录/www/wwwroot/www.tqazy.com/wp-content/themes/argon-theme-master
下创建文件:friend_rss.php。
内容如下:
<?php
/*
Template Name: 朋友圈
*/ ?>
<?php get_header(); ?>
<div class="page-information-card-container">
<div class="page-information-card card bg-gradient-secondary shadow-lg border-0">
<div class="card-body">
<h3 class="text-black"><?php _e('${页面名}', 'argon'); ?></h3>
<?php if (the_archive_description() != '') { ?>
<p class="text-black mt-3">
<?php the_archive_description(); ?>
</p>
<?php } ?>
<p class="text-black mt-3 mb-0 opacity-8">
<i class="fa fa-quote-left mr-1"></i>
<?php _e('${页面提示语}', 'argon'); ?>
</p>
</div>
</div>
</div>
<?php get_sidebar(); ?>
<div id="primary" class="content-area">
<main id="main" class="site-main" role="main">
<?php
// 定义自定义模板标签函数
function the_site_name() {
global $post;
echo esc_html($post->post_site_name);
}
function the_icon() {
global $post;
echo esc_url($post->post_icon);
}
// 获取JSON数据
$jsonData = file_get_contents('${json所在目录地址}');
// 检查文件读取是否成功
if ($jsonData!== false) {
// 将JSON数据解析为PHP数组
$articles = json_decode($jsonData, true);
if ($articles!== null) {
// 对文章按时间排序(最新的排在前面)
usort($articles, function ($a, $b) {
return strtotime($b['time']) - strtotime($a['time']);
});
// 设置每页显示的文章数量
$itemsPerPage = ${文章数量};
// 生成文章列表
$displayArticles = array_slice($articles, 0, $itemsPerPage);
if (!empty($displayArticles)) {
global $post;
foreach ($displayArticles as $article) {
// 模拟WordPress文章对象
// 将 $article['time'] 转换为 MySQL 时间戳格式
$formatted_time = date('Y-m-d H:i:s', strtotime($article['time']));
$new_article = " 作者:" . $article['site_name'];
$post = (object) [
'ID' => $article['id']?? uniqid(), // 如果JSON中没有id字段,使用唯一ID
'post_title' => "《" . $article['title'] . "》",
'post_date' => $formatted_time,
'post_content' => $article['description'],
'guid' => $article['link'],
'post_permalink' => $article['link'],
'post_site_name' => $new_article,
'post_icon' => $article['icon'],
];
setup_postdata($post); // 设置当前文章数据
get_template_part('template-parts/content', 'friend');
wp_reset_postdata(); // 重置文章数据
}
} else {
get_template_part('template-parts/content', 'none-tag');
}
} else {
get_template_part('template-parts/content', 'none-tag');
}
} else {
get_template_part('template-parts/content', 'none-tag');
}
?>
<?php get_footer(); ?>
替换项:
${页面名}
:页面名称,比如:朋友圈;${页面提示语}
:在页面名称下面显示的提示语,比如:这里是已交换友链的网站发布的内容,通过RSS方式订阅,每1小时更新一次内容。${json所在目录地址}
:前面rss.php生成的json所在地址,比如:/www/wwwroot/www.tqazy.com/friends_rss.json
;${文章数量}
:即你要展示的文章数量,比如:38。可以写50及以内,如果经常看朋友圈的话,设置展示太多了没意义。
四、配置“朋友圈”配套文件
在目录/www/wwwroot/www.tqazy.com/wp-content/themes/argon-theme-master/template-parts
下创建文件:content-friend.php,上面的文件用得到,属于样式配置。
内容如下,这个文件没有替换项:
<div class="shuoshuo-container">
<div class="shuoshuo-meta shadow-sm">
<span>
<i class="fa fa-calendar-o" aria-hidden="true"></i>
<span class="shuoshuo-date-month"><?php echo get_the_time('n')?></span> <?php _e('月', 'argon');?>
<span class="shuoshuo-date-date"><?php echo get_the_time('d')?></span> <?php _e('日', 'argon');?> ,
<span class="shuoshuo-date-year"><?php echo get_the_time('Y')?></span>
<div class="post-meta-devide">|</div>
<i class="fa fa-clock-o" aria-hidden="true"></i>
<span class="shuoshuo-date-time"><?php echo get_the_time('G:i:s')?></span>
</span>
<?php if ( is_sticky() ) : ?>
<div class="post-meta-devide">|</div>
<div class="post-meta-detail post-meta-detail-words">
<i class="fa fa-thumb-tack" aria-hidden="true"></i>
<?php _ex('置顶', 'pinned', 'argon');?>
</div>
<?php endif; ?>
</div>
<article class="card shuoshuo-main shuoshuo-foldable bg-white shadow-sm border-0" id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
<?php if ( get_the_title() != '' ) : ?>
<a class="shuoshuo-title" href="<?php the_guid(); ?>" target="_black"><?php the_title(); ?></a>
<font style="font-weight:900;"><?php the_site_name(); ?></font>
<?php endif; ?>
<div class="shuoshuo-content">
<?php the_content(); ?>
</div>
<?php
global $withcomments;
$withcomments = true;
// comments_template( '/comments-shuoshuo-preview.php' );
?>
</article>
</div>
此时,“朋友圈”的模板部分就编写完毕了。
在网站后台可以看到:外观 > 主题文件编辑器 > friend_rss.php
五、展示“朋友圈”页面
第一步:博客后台 > 页面 > 添加页面:
- 页面名称:朋友圈
- 正文不用填写任何内容
- 设置右边的页面属性:
- 父级:无
- 模板:朋友圈
- 发布
第二步:博客后台 > 外观 > 菜单 > 选择要编辑的菜单,然后在左侧的页面里找到“朋友圈”,添加到菜单,至于剩下的可以自行配置了。
比如我的朋友圈就是放在“友链”菜单下的:
到此,“朋友圈”功能已经成功的添加到了你的网站上了。
六、自动刷新朋友圈
最重要的一步来了,我们要想自动刷新朋友圈,而不是每次都去调用rss.php文件,那么我们就要做定时任务。
我也不知道为什么我单调rss.php文件是不能刷新FreshRSS的订阅源的,所以我做了两个定时任务。
我是使用宝塔的,如果您没有宝塔,可以通过写脚本方式实现。
打开宝塔面板 > 计划任务:
6.1 第一个定时任务
- 添加任务;
- 选择:Shell脚本;
- 任务名称:定时刷新FreshRSS;
- 执行周期:每小时 1分钟;
- 脚本内容:
php /www/wwwroot/rss.tqazy.com/app/actualize_script.php > /tmp/FreshRSS.log 2>&1
- 确定
需要把这里的/www/wwwroot/rss.tqazy.com
替换成你的rss解压文件所在的目录地址。
6.2 第二个定时任务
- 添加任务;
- 选择:访问URL-GET;
- 任务名称:朋友圈订阅数据更新;
- 执行周期:每小时 5分钟;
- URL地址:
https://xxx.com/xxx/xxx/rss.php
- 确定
需要把这里的https://xxx.com/xxx/xxx/rss.php
换成你的博客网站能访问到rss.php的域名地址。
你可以执行一下,看看日志,是否成功的写入了。
至此,朋友圈的功能全部完成。
效果:
该功能会将在FreshRSS中订阅的订阅源,以每小时1次的频率自动刷新订阅源并读取到friends_rss.json中,然后模板通过读取friends_rss.json文件将最新的n条文章展示到“朋友圈”的页面中。
注意:每次您的网站新增友链时,需要将对方的rss地址手动的添加到你的FreshRSS中。
感谢博主,不过博主的代码都不太健壮,比如rss.php文件,如果我的网站配置了ssl证书,强制转换为https,这个php文件好像不太兼容这种情况,我加载起来是有问题的,我用ai改了一下代码(俺不会php,也不知道ai怎么改的,哈哈哈),然后又让ai改了其它代码,功能就正常了。另外定时可以直接修改php加上定时功能,也是ai的,哈哈哈哈,好长一段,不过能跑,或者用第三方定时任务平台
对的,代码里应该是有很多不合理的地方,主要是我也不会php,这些代码还都是早期的ai给我生成的,那时候的ai没有现在的这么智能,而且还无法一次性生成我想要的,所以经过ai多次反复的修改,就留下了很多暗疾。不过在我这里能成功跑起来之后,我就没再去动它了,哈哈。