当歌 - RSS 订阅分发平台开发
以下将详细介绍当歌平台的技术架构、功能实现以及相关代码逻辑。
一、项目概述
当歌是一个极简的 RSS 订阅分发平台,旨在为用户提供便捷的 RSS 管理和订阅服务,帮助用户轻松获取和分享最新资讯。
二、技术架构
后端语言:PHP
数据库:MySQL,通过 PDO(PHP Data Objects)进行连接和操作,配置信息如下:
$host = 'localhost';
$db = 'rss';
$user = 'rss';
$pass = '123456';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,PDO::ATTR_EMULATE_PREPARES => false,
];
try {$pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
**邮件发送:**使用 PHPMailer 库来实现邮件发送功能,用于发送验证码和订阅推送邮件。
三、功能模块
(一)用户认证与登录
在 index.php、add_rss.php 等多个页面中,通过 session_start() 启动会话,并检查 $_SESSION['username'] 是否存在来判断用户是否登录。例如在 index.php 中:
session_start();
$isLoggedIn = isset($_SESSION['username']);
如果用户已登录,导航栏会显示用户名及退出登录选项;未登录时则显示登录和注册链接。
(二)订阅管理
添加订阅

在 add_rss.php 中,首先获取用户 ID,若用户未登录则提示先登录。然后检查用户是否已有密钥,若无则生成一个新的密钥并存储到 user_keys 表中。
当用户提交 RSS URL 时,会检查是否已订阅该 URL,若未订阅且 URL 可访问,则将订阅信息插入到 subscriptions 表中,并触发 update_rss.php 进行 RSS 内容更新。
部分关键代码如下:
// 获取用户ID
$stmt = $pdo->prepare('SELECT id FROM users WHERE username =?');
$stmt->execute([$username]);
$user = $stmt->fetch();
$userId = $user['id'];// 检查用户是否已有密钥
$stmt = $pdo->prepare('SELECT user_key FROM user_keys WHERE user_id =?');
$stmt->execute([$userId]);
$userKey = $stmt->fetchColumn();
if (!$userKey) {// 生成新密钥并插入$userKey = bin2hex(random_bytes(16));$stmt = $pdo->prepare('INSERT INTO user_keys (user_id, user_key) VALUES (?,?)');$stmt->execute([$userId, $userKey]);
}// 处理添加订阅请求
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['rss_url'])) {$rssUrl = $_POST['rss_url']?? '';if ($rssUrl && $userId) {// 检查是否已订阅$stmt = $pdo->prepare('SELECT COUNT(*) FROM subscriptions WHERE user_id =? AND rss_url =?');$stmt->execute([$userId, $rssUrl]);$count = $stmt->fetchColumn();if ($count > 0) {echo "<div class='alert alert-warning'>您已订阅此 RSS 源</div>";} else {// 验证 RSS URL 是否可访问$rss = @simplexml_load_file($rssUrl);if ($rss) {// 添加订阅关系$stmt = $pdo->prepare('INSERT INTO subscriptions (user_id, rss_url) VALUES (?,?)');$stmt->execute([$userId, $rssUrl]);// 触发更新$updateUrl = "https://dang.ge/update_rss.php?key={$userKey}";$response = @file_get_contents($updateUrl);if ($response === FALSE) {echo "<div class='alert alert-danger'>无法调用更新服务,请检查配置。</div>";} else {echo "<div class='alert alert-success'>订阅添加成功,并已更新。</div>";}// 刷新页面header("Location: add_rss.php");exit;} else {echo "<div class='alert alert-danger'>无法访问该 RSS 源,请检查 URL 是否正确</div>";}}}
}
删除订阅

当用户提交要删除的 RSS URL 时,会从 subscriptions 表和 rss_items 表中删除相关记录,并刷新页面以反映删除操作。
代码示例:
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_rss_url'])) {$rssUrlToDelete = $_POST['delete_rss_url']?? '';if ($rssUrlToDelete && $userId) {$stmt = $pdo->prepare('DELETE FROM subscriptions WHERE user_id =? AND rss_url =?');$stmt->execute([$userId, $rssUrlToDelete]);// 删除相关 RSS 内容$stmt = $pdo->prepare('DELETE FROM rss_items WHERE user_id =? AND rss_url =?');$stmt->execute([$userId, $rssUrlToDelete]);// 刷新页面header("Location: add_rss.php");exit;}
}
订阅列表展示
从 subscriptions 表中获取用户的订阅信息,并在页面上以列表形式展示,每个订阅项包含订阅源标题和删除按钮。点击订阅源标题可查看该订阅的内容。
相关代码如下:
// 获取订阅信息
$stmt = $pdo->prepare('SELECT rss_url FROM subscriptions WHERE user_id =?');
$stmt->execute([$userId]);
$subscriptions = $stmt->fetchAll();// 展示订阅列表
foreach ($subscriptions as $subscription) {$rssUrl = $subscription['rss_url'];$rss = simplexml_load_file($rssUrl);$channelTitle = $rss->channel->title?? $rss->title?? '未知标题';?><li class="list-group-item d-flex justify-content-between align-items-center"><form method="post" action="" class="d-inline"><input type="hidden" name="selected_rss" value="<?php echo htmlspecialchars($rssUrl);?>"><button type="submit" class="btn btn-link p-0"><?php echo htmlspecialchars($channelTitle);?></button></form><form method="post" action="" class="d-inline"><input type="hidden" name="delete_rss_url" value="<?php echo htmlspecialchars($rssUrl);?>"><button type="submit" class="btn btn-danger btn-sm">删除</button></form></li><?php
}
(三)RSS 内容更新与推送
更新机制
在 update_rss.php 中,根据用户密钥获取用户 ID,然后获取用户的所有订阅 RSS URL。对于每个 URL,先加载 RSS 内容,检测其格式(Atom 或其他)并获取条目。
接着获取已存在的链接,对比新条目链接,若不存在则插入到 rss_items 表中,并构建邮件内容。
关键代码如下:
$key = $_GET['key']?? null;
if (!$key) {echo "无效的请求";exit;
}
// 验证密钥
$stmt = $pdo->prepare('SELECT user_id FROM user_keys WHERE user_key =?');
$stmt->execute([$key]);
$userKey = $stmt->fetch();
if (!$userKey) {echo "无效的密钥";exit;
}
$userId = $userKey['user_id'];// 获取用户的订阅
$stmt = $pdo->prepare('SELECT DISTINCT rss_url FROM subscriptions WHERE user_id =?');
$stmt->execute([$userId]);
$rssUrls = $stmt->fetchAll();foreach ($rssUrls as $rssUrl) {$url = $rssUrl['rss_url'];$rss = @simplexml_load_file($url);if ($rss) {// 检测格式并获取条目$isAtom = $rss->getNamespaces(true)[''] === 'http://www.w3.org/2005/Atom';$items = $isAtom? ($rss->entry?? []) : ($rss->channel->item?? []);// 获取已存在链接$stmt = $pdo->prepare('SELECT link FROM rss_items WHERE user_id =? AND rss_url =?');$stmt->execute([$userId, $url]);$existingLinks = $stmt->fetchAll(PDO::FETCH_COLUMN);$newCount = 0;$skipCount = 0;$emailBody = "<h1>当歌 Rss 订阅推送</h1><ul>";foreach ($items as $item) {// 获取链接及其他字段$link = $isAtom? (string)($item->link['href']?? $item->link?? '#') : (string)($item->link?? '#');if (in_array($link, $existingLinks)) {$skipCount++;continue;}$title = (string)($item->title?? '无标题');$content = $isAtom? (string)($item->content?? $item->summary?? '') : (string)($item->{'content:encoded'}?? $item->description?? '');$pubDate = $isAtom? (string)($item->published?? $item->updated?? date('Y-m-d H:i:s')) : (string)($item->pubDate?? date('Y-m-d H:i:s'));try {$timestamp = strtotime($pubDate);$formattedDate = $timestamp? date('Y-m-d H:i:s', $timestamp) : date('Y-m-d H:i:s');// 插入新记录$stmt = $pdo->prepare('INSERT INTO rss_items (user_id, rss_url, title, link, description, pub_date) VALUES (?,?,?,?,?,?)');$stmt->execute([$userId,$url,$title,$link,$content,$formattedDate]);$newCount++;$emailBody.= "<li><strong>文章标题:</strong> {$title}<br>"."<strong>发布时间:</strong> {$formattedDate}<br>"."<strong>文章地址:</strong> <a href='{$link}'>查看原文</a></li>";} catch (PDOException $e) {error_log("插入 RSS 条目失败: ". $e->getMessage());continue;}}$emailBody.= "</ul>";// 检查是否首次执行及发送邮件//...}
}
邮件推送
若有新内容且不是首次执行,获取用户主邮箱和所有订阅邮箱,合并去重后,使用 PHPMailer 发送邮件通知,邮件内容包含新文章的标题、发布时间和链接。
(四)邮件订阅功能
订阅流程

在 subscribe.php 中,首先根据传入的密钥获取用户 ID 和用户名,然后展示用户的订阅标题信息。
当用户提交邮箱时,会检查是否在冷却时间内(60 秒),若不在则发送验证码到邮箱,并记录相关信息到会话中。
当用户提交验证码时,会验证验证码是否正确,若正确则将订阅信息插入到 email_subscriptions 表中。
关键代码如下:
$key = $_GET['key']?? null;
if (!$key) {echo "<div class='alert alert-danger'>无效的请求</div>";exit;
}
// 验证密钥
$stmt = $pdo->prepare('SELECT user_id FROM user_keys WHERE user_key =?');
$stmt->execute([$key]);
$userKey = $stmt->fetch();
if (!$userKey) {echo "<div class='alert alert-danger'>无效的密钥</div>";exit;
}
$userId = $userKey['user_id'];// 获取用户名
$stmt = $pdo->prepare('SELECT username FROM users WHERE id =?');
$stmt->execute([$userId]);
$user = $stmt->fetch();
$keyUsername = $user['username'];// 获取订阅标题
$stmt = $pdo->prepare('SELECT title, pub_date FROM rss_items WHERE user_id =? ORDER BY pub_date DESC LIMIT 5');
$stmt->execute([$userId]);
$subscriptions = $stmt->fetchAll();if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['email'])) {$currentTime = time();if ($currentTime - $lastRequestTime < $cooldown) {$message = "<div class='alert alert-warning'>请稍后 60 秒后再试。</div>";} else {$email = $_POST['email'];$verificationCode = rand(100000, 999999);// 发送验证码邮件$mail = new PHPMailer(true);try {$mail->isSMTP();$mail->Host ='smtp.163.com';$mail->SMTPAuth = true;$mail->Username = 'xxxxx@163.com';$mail->Password = 'xxxxx';$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;$mail->Port = 25;$mail->CharSet = 'UTF-8';$mail->setFrom('xxxxx@163.com', 'RSS Notifier');$mail->addAddress($email);$mail->isHTML(true);$mail->Subject = '当歌 Rss 订阅平台订阅验证码';$mail->Body = "您的验证码是:<strong>{$verificationCode}</strong>";$mail->send();$message = "<div class='alert alert-success'>验证码已发送到您的邮箱,请查收。</div>";$showVerification = true;$_SESSION['last_request_time'] = $currentTime;} catch (Exception $e) {$message = "<div class='alert alert-danger'>邮件发送失败: {$mail->ErrorInfo}</div>";}$_SESSION['verification_code'] = $verificationCode;$_SESSION['email'] = $email;}
}if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['verification_code'])) {$inputCode = $_POST['verification_code'];if ($inputCode == $_SESSION['verification_code']) {// 验证成功,订阅$stmt = $pdo->prepare('INSERT INTO email_subscriptions (user_id, email, key_username, subscribed_at) VALUES (?,?,?, NOW())');$stmt->execute([$userId, $_SESSION['email'], $keyUsername]);$message = "<div class='alert alert-success'>订阅成功!</div>";} else {$message = "<div class='alert alert-danger'>验证码错误,请重试。</div>";}
}
平台地址
Dang.Ge
相关文章:
当歌 - RSS 订阅分发平台开发
以下将详细介绍当歌平台的技术架构、功能实现以及相关代码逻辑。 一、项目概述 当歌是一个极简的 RSS 订阅分发平台,旨在为用户提供便捷的 RSS 管理和订阅服务,帮助用户轻松获取和分享最新资讯。 二、技术架构 后端语言:PHP 数据库&#…...
学习threejs,导入wrl格式的模型
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️THREE.VRMLLoader wrl模型加…...
使用GitLab+Jenkins搭建CICD执行环境
使用GitLabJenkins搭建CI\CD执行环境 前言什么是DevOps?什么是CI/CD?使用GitLabJenkins搭建CI\CD执行环境GitLab安装1. 安装和配置所需的依赖2. 下载并安装极狐GitLab3. 登录极狐GitLab 实例4.常用gitlab指令5.修改密码 Jenkins安装1.Jenkins 的主要特点…...
使用vue-pdf预览pdf和解决pdf电子签章显示问题
使用vue-pdf预览pdf和解决pdf电子签章显示问题 第一步:npm install vue-pdf 第二步页面使用vue-pdf <template><div class"pdf1"><Pdf v-for"i in numPages" :key"i" :src"src" :page"i" />…...
【Rust自学】11.3. 自定义错误信息
喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(・ω・) 11.3.1. 添加错误信息 在 11.2. 断言(Assert) 中我们学习了assert!、assert_eq!和assert_ne!这三个宏,而这篇文章讲的就是它…...
05、Docker学习,常用安装:Mysql、Redis、Nginx、Nacos
Docker学习,常用安装:Mysql、Redis、Nginx、Nacos 一、Docker安装Mysql 1、docker search mysql ##查找mysql版本都有哪些 2、docker pull mysql:5.6 ##下载5.6版本的mysql镜像 3、docker run -p 13306:3306 --name mysql ##运行…...
RabbitMQ高级篇之MQ可靠性 数据持久化
文章目录 消息丢失的原因分析内存存储的缺陷如何确保 RabbitMQ 的消息可靠性?数据持久化的三个方面持久化对性能的影响持久化实验验证性能对比Spring AMQP 默认持久化总结 消息丢失的原因分析 RabbitMQ 默认使用内存存储消息,但这种方式带来了两个主要问…...
leetcode 2274. 不含特殊楼层的最大连续楼层数 中等
Alice 管理着一家公司,并租用大楼的部分楼层作为办公空间。Alice 决定将一些楼层作为 特殊楼层 ,仅用于放松。 给你两个整数 bottom 和 top ,表示 Alice 租用了从 bottom 到 top(含 bottom 和 top 在内)的所有楼层。另…...
Tauri教程-基础篇-第二节 Tauri的核心概念上篇
“如果结果不如你所愿,就在尘埃落定前奋力一搏。”——《夏目友人帐》 “有些事不是看到了希望才去坚持,而是因为坚持才会看到希望。”——《十宗罪》 “维持现状意味着空耗你的努力和生命。”——纪伯伦 Tauri 技术教程 * 第四章 Tauri的基础教程 第二节…...
大风车excel:怎么把题库导入excel?题库导入excel
高效管理试题库:如何批量导入试题到 Excel? 在教育培训、学校管理以及在线学习平台中,试题库的管理是核心工作之一。如何快速、准确地将试题导入到 Excel 表格中,成为许多教育工作者和开发者的迫切需求。本文将围绕“题库导入 Ex…...
Java 兼容读取WPS和Office图片,结合EasyExcel读取单元格信息
在Java开发中,处理Excel文件中的图片(包括浮动图片和嵌入图片)是一个常见的需求。本文将介绍如何使用EasyExcel和Apache POI库来读取Excel文件中的图片,并将其与数据进行关联。 1. 引言 在许多应用场景中,Excel文件不…...
电脑硬盘系统迁移及问题处理
一、系统迁移准备 1、确认你的电脑主板是否支持安装两块硬盘,如电脑主板有多个M2硬盘接口,我们将新硬盘安装到主板上,原来的老硬盘安装在第二个接口上,主板只有一个M2接口的话可以使用移动硬盘盒。 2、新硬盘安装好后,我们进入原来的系统,在 此电脑–右键–管理–磁盘管…...
网关 + Nacos配置管理
网关 网关:就是网络的关口,负责请求的路由、转发、身份校验。 网关路由 新建网关模块gateway引入相关依赖 <!--网关--> <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter…...
《Spring Framework实战》6:核心技术 4.1.IoC 容器
欢迎观看《Spring Framework实战》视频教程 本章介绍 Spring 的控制反转 (IoC) 容器。 本部分摘要 Spring IoC 容器和 Bean 简介 容器概述 Bean 概述 依赖 Bean 作用域 自定义 Bean 的性质 Bean 定义继承 容器扩展点 基于注解的容器配置 Clas…...
ModuleNotFoundError: No module named ‘audioop‘
问题 ModuleNotFoundError: No module named pyaudioop ModuleNotFoundError: No module named audioop解决方案 安装库 pip3 install audioop-lts...
STM32-笔记38-I2C-oled实验
一、什么是I2C? I2C总线,全称Inter-Integrated Circuit(互连集成电路),是一种由Philips(现NXP半导体)公司在1980年代初开发的同步 串行 半双工通信总线。 二、有了串口通信为什么要使用I2C&…...
人大金仓实现主键自增.
使用数据库中自带的参数类型 serial 类型(相当于创建一个INT列), 或者bigserial(相当于创建一个BIGINT列. 示例sql: CREATE TABLE ord(id SERIAL,ord_no INT NOT NULL,ord_name VARCHAR(32),CONSTRAINT "ord_PKEY" PRIMARY KEY ("id"));插入时指定自增值…...
h264之多视点mvc编码及解码过程(JMVC平台举例)
h264标准参考平台JMVC是针对MVC标准的,JMVC支持多视点编码、合流、多视点解码操作。可以利用JMVC生成h264 mvc码流和解码。 JMVC的下载地址是:jvet / JMVC GitLabH.264/AVC multi-view coding (MVC) extension JMVC reference softwarehttps://vcgit.hh…...
小程序学习08—— 系统参数获取和navBar组件样式动态设置
一 系统信息的概念 uni-app提供了异步(uni.getSystemInfo)和同步(uni.getSystemInfoSync)的2个API获取系统信息。 success 返回参数说明: 参数分类说明statusBarHeight手机状态栏的高度system操作系统名称及版本。。。 二 自定义navbar 2.1 获取系统参数 代码展示…...
数据库环境安装(day1)
网址:MySQL 下载(环境准备): (2-5点击此处,然后选择合适的版本) 1.linux在线YUM仓库 下载/安装: wget https://repo.mysql.com//mysql84-community-release-el9-1.noarch.rpm rpm -i https://r…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道
文/法律实务观察组 在债务重组领域,专业机构的核心价值不仅在于减轻债务数字,更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明,合法债务优化需同步实现三重平衡: 法律刚性(债…...
Spring Boot + MyBatis 集成支付宝支付流程
Spring Boot MyBatis 集成支付宝支付流程 核心流程 商户系统生成订单调用支付宝创建预支付订单用户跳转支付宝完成支付支付宝异步通知支付结果商户处理支付结果更新订单状态支付宝同步跳转回商户页面 代码实现示例(电脑网站支付) 1. 添加依赖 <!…...
React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构
React 实战项目:微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇!在前 29 篇文章中,我们从 React 的基础概念逐步深入到高级技巧,涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...

