当歌 - 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…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...

(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...

算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...

手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...
LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)
在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...

使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...