说来如果时间不充裕,真的不能轻易去折腾主题,只要仔细找,这 bug 是一个接一个好像永远解决不完,这两天抽时间一个是解决了以前瞎折腾造成的切换标签返回顶部动画失效和在非笔记标签下点击话题列表时不显示关闭话题问题。
另一个是对养眼标签视频的优化,现在无论 PC 还是移动端均采用了 fancybox 灯箱弹出播放的效果,首先由于有的视频不适合用第一帧作为封面,又增加了一个后台可以选择图片作为视频封面的功能。又由于视频有的是 16:9 格式有的又是 9:16 格式,为了都能适配翻来覆去改了好几次,目前只能达到既有效果了,就这样吧,就让养眼功能到此为止吧。
回到文章题目,以前没怎么留意,也就前几天才觉得友圈加载速度有点慢,开始还以为是自己最近频繁折腾主题代码造成的,后来无意中发现 output.json 怎么这么大竟然有几十兆,再看网上抄来的 rss.php 代码,原来代码中设置了从后端获取 1000 篇文章的数值…因此即使只是获取前 30 篇,浏览器都要加载并解析整个 json 文件,才造成加载缓慢。
起初也没多想,直接将 1000 改为了 30,果然速度明显提升。但是今天在增加新订阅的时候又发现问题了…新增加的订阅无论是什么时间发布的文章 freshrss 都会将其作为前 30 篇发送给 rss.php,可见后端并没有先全局按原文发布时间排序后再提供这 30 篇。造成在前端排序只能排序这 30 篇,并不能“从所有文章里”选出最新的 30 篇。
优化的大体思路是首先还是要一次性的获取大量文章(默认 1000 篇)-> 然后按发布时间降序排序-> 最终只需要改为仅保存排好序的前 30 篇到 json 文件即可。(考虑到有的博客文字是真的多,因此为了进一步压缩 json 文件,又设置了只保存摘要的前 200 个字符)友圈的具体实现方法就不写了,网上一大堆。主要记录一下优化后的 rss.php 文件代码,里面有详细的注释供参考:
<?php /** * 获取所有已订阅的文章并保存为 JSON 文件 * * @param string $user 用户名 * @param string $password 密码 * @param int $articleCount 获取的文章数量,默认为 1000 */ function getAllSubscribedArticlesAndSaveToJson($user, $password, $articleCount = 1000) { // 基础 API 地址 $apiUrl = '你部署FreshRSS的域名/api/greader.php'; // 拼接获取文章列表的接口 URL(n=获取文章数) $articlesUrl = $apiUrl . '/reader/api/0/stream/contents/reading-list?&n=' . $articleCount; // 拼接登录接口 URL,带用户名和密码 $loginUrl = $apiUrl . '/accounts/ClientLogin?Email=' . urlencode($user) . '&Passwd=' . urlencode($password); // 调用登录请求函数,获取返回内容 $loginResponse = curlRequest($loginUrl); // 如果返回内容中包含 'Auth=',表示登录成功 if (strpos($loginResponse, 'Auth=') !== false) { // 从返回内容截取出授权令牌 $authToken = substr($loginResponse, strpos($loginResponse, 'Auth=') + 5); // 使用授权令牌调用获取文章列表接口 $articlesResponse = curlRequest($articlesUrl, $authToken); // 将 JSON 格式内容转换为数组 $articles = json_decode($articlesResponse, true); // 判断是否拿到了文章列表 if (isset($articles['items'])) { // 准备一个空数组,用于存放格式化后的文章 $formattedArticles = array(); // 遍历所有文章数据 foreach ($articles['items'] as $item) { // 发布时间戳 $publishedTime = isset($item['published']) ? $item['published'] : 0; // 原始摘要(HTML) $rawSummary = isset($item['summary']['content']) ? $item['summary']['content'] : ''; // 去除 HTML 标签及转义字符 $full_desc = strip_tags(html_entity_decode($rawSummary, ENT_QUOTES, 'UTF-8')); // 截取前 200 个字符作为描述 $maxLength = 200; $description = mb_substr($full_desc, 0, $maxLength, 'UTF-8'); // 计算相对时间 (xx分钟前/xx天前等) $relativeTime = timeAgo($publishedTime); // 从链接中解析出站点 URL $site_url = ''; if (isset($item['alternate'][0]['href'])) { $parsed_url = parse_url($item['alternate'][0]['href']); $site_url = $parsed_url['scheme'] . '://' . $parsed_url['host']; } // 将整理好的字段放入数组 $formattedArticles[] = array( 'site_name' => isset($item['origin']['title']) ? $item['origin']['title'] : '', 'title' => isset($item['title']) ? $item['title'] : '', 'link' => isset($item['alternate'][0]['href']) ? $item['alternate'][0]['href'] : '', 'site_url' => $site_url, 'time' => $relativeTime, // 相对时间 'published' => $publishedTime, // 时间戳 'description' => $description, ); } // 按发布时间降序排序 usort($formattedArticles, function ($a, $b) { return $b['published'] - $a['published']; }); // 取前 30 篇文章 $top30 = array_slice($formattedArticles, 0, 30); // 保存到 JSON 文件 saveToJsonFile($top30, 'output.json'); // 输出提示 echo "\n已将按发布时间降序的前 30 篇文章保存到 output.json。\n"; return; } else { // 如果没有 items 字段,则报错 echo "Error retrieving articles.\n"; } } else { // 登录失败 echo "Login failed.\n"; } } /** * 使用 cURL 发起 HTTP 请求 * * @param string $url 请求的 URL * @param string $authToken 授权令牌,可选 * @return string 返回的响应内容 */ function curlRequest($url, $authToken = null) { // 初始化 cURL $ch = curl_init($url); // 如果存在授权令牌,则设置到请求头 if ($authToken) { $headers = array( 'Authorization: GoogleLogin auth=' . $authToken, ); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); } // 设置返回内容为字符串而非直接输出 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 执行请求 $response = curl_exec($ch); // 如果有错误,输出错误信息 if (curl_errno($ch)) { echo 'cURL Error: ' . curl_error($ch); } // 关闭 cURL curl_close($ch); // 返回响应结果 return $response; } /** * 将数据保存为 JSON 文件 * * @param array $data 要写入文件的数组 * @param string $filename 文件名,默认为 output.json */ function saveToJsonFile($data, $filename = 'output.json') { // 将数组转换为 JSON 字符串(带格式化和中文不转义) $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); // 写入文件并检测结果 if (file_put_contents($filename, $json) !== false) { echo "{$filename} 写入成功。\n"; } else { echo "{$filename} 写入失败。\n"; } } /** * 将时间戳转换为相对时间 * * @param int $timestamp 时间戳 * @return string 相对时间描述 */ function timeAgo($timestamp) { $now = time(); $diff = $now - $timestamp; if ($diff < 60) { return $diff . '秒前'; } elseif ($diff < 3600) { $minutes = floor($diff / 60); return $minutes . '分钟前'; } elseif ($diff < 86400) { $hours = floor($diff / 3600); return $hours . '小时前'; } elseif ($diff < 2592000) { $days = floor($diff / 86400); return $days . '天前'; } elseif ($diff < 31536000) { $months = floor($diff / 2592000); return $months . '个月前'; } else { $years = floor($diff / 31536000); return $years . '年前'; } } // 调用主函数示例 getAllSubscribedArticlesAndSaveToJson('这里是FreshRSS的用户名', '这里是FreshRSS的API密码', 1000);
其实还可以考虑获取所以订阅的文章,然后将文章按发布时间顺序以 30 篇为一份,一份份的全都存为 json 文件,这样就可以方便实现友圈分页功能。但又觉得既费力又没啥用处,还是不折腾了。