当歌 - 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…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...
AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...
通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器
拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件: 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...

