当前位置: 首页 > news >正文

当歌 - RSS 订阅分发平台开发

以下将详细介绍当歌平台的技术架构、功能实现以及相关代码逻辑。
微信图片_20250108191058.png

一、项目概述

当歌是一个极简的 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.phpadd_rss.php 等多个页面中,通过 session_start() 启动会话,并检查 $_SESSION['username'] 是否存在来判断用户是否登录。例如在 index.php 中:

session_start();
$isLoggedIn = isset($_SESSION['username']);

如果用户已登录,导航栏会显示用户名及退出登录选项;未登录时则显示登录和注册链接。

(二)订阅管理

添加订阅
微信图片_20250108191151.png
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>";}}}
}

删除订阅
微信图片_20250108191236.png
当用户提交要删除的 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 发送邮件通知,邮件内容包含新文章的标题、发布时间和链接。

(四)邮件订阅功能

订阅流程
微信图片_20250108191328.png
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 订阅分发平台&#xff0c;旨在为用户提供便捷的 RSS 管理和订阅服务&#xff0c;帮助用户轻松获取和分享最新资讯。 二、技术架构 后端语言&#xff1a;PHP 数据库&#…...

学习threejs,导入wrl格式的模型

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.VRMLLoader wrl模型加…...

使用GitLab+Jenkins搭建CICD执行环境

使用GitLabJenkins搭建CI\CD执行环境 前言什么是DevOps&#xff1f;什么是CI/CD&#xff1f;使用GitLabJenkins搭建CI\CD执行环境GitLab安装1. 安装和配置所需的依赖2. 下载并安装极狐GitLab3. 登录极狐GitLab 实例4.常用gitlab指令5.修改密码 Jenkins安装1.Jenkins 的主要特点…...

使用vue-pdf预览pdf和解决pdf电子签章显示问题

使用vue-pdf预览pdf和解决pdf电子签章显示问题 第一步&#xff1a;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. 自定义错误信息

喜欢的话别忘了点赞、收藏加关注哦&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 11.3.1. 添加错误信息 在 11.2. 断言(Assert) 中我们学习了assert!、assert_eq!和assert_ne!这三个宏&#xff0c;而这篇文章讲的就是它…...

05、Docker学习,常用安装:Mysql、Redis、Nginx、Nacos

Docker学习&#xff0c;常用安装&#xff1a;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 的消息可靠性&#xff1f;数据持久化的三个方面持久化对性能的影响持久化实验验证性能对比Spring AMQP 默认持久化总结 消息丢失的原因分析 RabbitMQ 默认使用内存存储消息&#xff0c;但这种方式带来了两个主要问…...

leetcode 2274. 不含特殊楼层的最大连续楼层数 中等

Alice 管理着一家公司&#xff0c;并租用大楼的部分楼层作为办公空间。Alice 决定将一些楼层作为 特殊楼层 &#xff0c;仅用于放松。 给你两个整数 bottom 和 top &#xff0c;表示 Alice 租用了从 bottom 到 top&#xff08;含 bottom 和 top 在内&#xff09;的所有楼层。另…...

Tauri教程-基础篇-第二节 Tauri的核心概念上篇

“如果结果不如你所愿&#xff0c;就在尘埃落定前奋力一搏。”——《夏目友人帐》 “有些事不是看到了希望才去坚持&#xff0c;而是因为坚持才会看到希望。”——《十宗罪》 “维持现状意味着空耗你的努力和生命。”——纪伯伦 Tauri 技术教程 * 第四章 Tauri的基础教程 第二节…...

大风车excel:怎么把题库导入excel?题库导入excel

高效管理试题库&#xff1a;如何批量导入试题到 Excel&#xff1f; 在教育培训、学校管理以及在线学习平台中&#xff0c;试题库的管理是核心工作之一。如何快速、准确地将试题导入到 Excel 表格中&#xff0c;成为许多教育工作者和开发者的迫切需求。本文将围绕“题库导入 Ex…...

Java 兼容读取WPS和Office图片,结合EasyExcel读取单元格信息

在Java开发中&#xff0c;处理Excel文件中的图片&#xff08;包括浮动图片和嵌入图片&#xff09;是一个常见的需求。本文将介绍如何使用EasyExcel和Apache POI库来读取Excel文件中的图片&#xff0c;并将其与数据进行关联。 1. 引言 在许多应用场景中&#xff0c;Excel文件不…...

电脑硬盘系统迁移及问题处理

一、系统迁移准备 1、确认你的电脑主板是否支持安装两块硬盘,如电脑主板有多个M2硬盘接口,我们将新硬盘安装到主板上,原来的老硬盘安装在第二个接口上,主板只有一个M2接口的话可以使用移动硬盘盒。 2、新硬盘安装好后,我们进入原来的系统,在 此电脑–右键–管理–磁盘管…...

网关 + Nacos配置管理

网关 网关&#xff1a;就是网络的关口&#xff0c;负责请求的路由、转发、身份校验。 网关路由 新建网关模块gateway引入相关依赖 <!--网关--> <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter…...

《Spring Framework实战》6:核心技术 4.1.IoC 容器

欢迎观看《Spring Framework实战》视频教程 本章介绍 Spring 的控制反转 &#xff08;IoC&#xff09; 容器。 本部分摘要 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&#xff1f; I2C总线&#xff0c;全称Inter-Integrated Circuit&#xff08;互连集成电路&#xff09;&#xff0c;是一种由Philips&#xff08;现NXP半导体&#xff09;公司在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标准的&#xff0c;JMVC支持多视点编码、合流、多视点解码操作。可以利用JMVC生成h264 mvc码流和解码。 JMVC的下载地址是&#xff1a;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 返回参数说明&#xff1a; 参数分类说明statusBarHeight手机状态栏的高度system操作系统名称及版本。。。 二 自定义navbar 2.1 获取系统参数 代码展示…...

数据库环境安装(day1)

网址&#xff1a;MySQL 下载&#xff08;环境准备&#xff09;&#xff1a; &#xff08;2-5点击此处&#xff0c;然后选择合适的版本&#xff09; 1.linux在线YUM仓库 下载/安装: wget https://repo.mysql.com//mysql84-community-release-el9-1.noarch.rpm rpm -i https://r…...

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...

C++_核心编程_多态案例二-制作饮品

#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为&#xff1a;煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例&#xff0c;提供抽象制作饮品基类&#xff0c;提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

【杂谈】-递归进化:人工智能的自我改进与监管挑战

递归进化&#xff1a;人工智能的自我改进与监管挑战 文章目录 递归进化&#xff1a;人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管&#xff1f;3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查

在对接支付宝API的时候&#xff0c;遇到了一些问题&#xff0c;记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

1688商品列表API与其他数据源的对接思路

将1688商品列表API与其他数据源对接时&#xff0c;需结合业务场景设计数据流转链路&#xff0c;重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点&#xff1a; 一、核心对接场景与目标 商品数据同步 场景&#xff1a;将1688商品信息…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命

在华东塑料包装行业面临限塑令深度调整的背景下&#xff0c;江苏艾立泰以一场跨国资源接力的创新实践&#xff0c;重新定义了绿色供应链的边界。 跨国回收网络&#xff1a;废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点&#xff0c;将海外废弃包装箱通过标准…...