直接用 response()->view() 返回 xml 视图最简单:需设置 content-type 为 application/rss+xml,视图以

直接用 response()->view() 返回 XML 视图最简单
Laravel 本身不内置 RSS 生成器,但不需要额外包也能快速输出合法 RSS XML。关键不是“生成 RSS”,而是“返回符合 RSS 2.0 规范的 XML 响应”。response()->view() 配合正确的 Content-Type 和视图结构就能搞定,比引入 spatie/laravel-feed 这类包更轻量、更可控。
-
路由中直接绑定控制器方法,比如
Route::get('/feed', [FeedController::class, 'index']); - 控制器里用
response()->view('feed.rss', $data)->header('Content-Type', 'application/rss+xml; charset=utf-8'); - 视图文件
resources/views/feed/rss.blade.php必须以<?xml version="1.0" encoding="UTF-8"?>开头,且不能有任何 PHP 输出或空格前置
RSS Blade 视图必须手动写全 <rss></rss> 结构,不能依赖模板继承
Blade 模板继承(如 @extends('layouts.app'))会带入 HTML 的 、 等标签,直接破坏 RSS XML 格式,导致浏览器/阅读器解析失败。RSS 是纯 XML,必须从根节点开始手写。
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ config('app.name') }} 博客</title>
<link>{{ url('/') }}</link>
<description>最新文章更新</description>
<language>zh-cn</language>
<lastBuildDate>{{ now()->toRssString() }}</lastBuildDate>
<atom:link href="{{ url('/feed') }}" rel="self" type="application/rss+xml" />
@foreach ($posts as $post)
<item>
<title>{{ $post->title }}</title>
<link>{{ $post->url }}</link>
<guid>{{ $post->url }}</guid>
<pubDate>{{ $post->published_at->toRssString() }}</pubDate>
<description>{!! strip_tags($post->excerpt) !!}</description>
</item>
@endforeach
</channel>
</rss>
strip_tags() 和 htmlspecialchars() 必须配合使用,否则 XML 解析报错
RSS 的 <description></description> 字段允许 HTML 片段,但原始内容里的双引号、尖括号、& 符号会直接破坏 XML 结构。只用 strip_tags() 不够,残留的 & 会被当成实体开头,导致解析中断;只用 htmlspecialchars() 又会让本该保留的链接失效。
- 安全做法:先
strip_tags()清除所有标签,再对结果调用htmlspecialchars($str, ENT_NOQUOTES, 'UTF-8') - 若需保留少量 HTML(如
<a></a>、<strong></strong>),改用html_entity_decode()+ 白名单过滤(推荐league/html-to-markdown或自定义正则),但 RSS 阅读器兼容性差,不建议 - 时间字段必须用
$model->published_at->toRssString(),不能用format('r')—— 后者可能输出非标准时区格式
缓存 RSS 响应要用 Response::cache() 而非视图缓存
RSS 内容变化频率低,但每次请求都重建 XML 视图浪费 CPU。不能用 View::share() 或 Blade 缓存(@cache),因为 XML 响应头(Content-Type)和内容必须原子化缓存。正确方式是用 Laravel 响应级缓存:
public function index()
{
$posts = Post::published()->latest('published_at')->take(20)->get();
return response()
->view('feed.rss', ['posts' => $posts])
->header('Content-Type', 'application/rss+xml; charset=utf-8')
->cache([
'etag' => md5($posts->first()?->updated_at ?? ''),
'max_age' => 3600,
]);
}
注意:Etag 基于数据变更时间生成,避免缓存过期后仍返回旧内容;max_age 设为 3600 秒(1 小时)是 RSS 阅读器普遍接受的刷新间隔,设太长会导致新文章延迟出现。
最后提醒:别在本地开发环境测试 RSS 效果——很多浏览器会把 application/rss+xml 自动跳转到自己的阅读器页面,看不到原始 XML。用 curl -I http://localhost/feed 检查响应头,或用 VS Code 插件 “XML Tools” 格式化响应体,才能确认结构是否合法。









