1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
|
# NewsNow RSS订阅源添加技术框架与解决方案
## 技术框架概述
NewsNow采用基于文件系统的RSS源管理方式,每个RSS源对应一个TypeScript文件,通过统一的配置系统和解析框架实现RSS内容的获取与处理。
## 核心组件
### 1. RSS获取工具 (`server/utils/rss-fetch.ts`)
- 功能:封装fetch API,提供统一的RSS内容获取
- 特性:
- 自动处理重定向
- 支持自定义User-Agent
- 错误处理和重试机制
- 返回原始XML文本供解析
### 2. RSS解析框架
- 基于fast-xml-parser库
- 支持RSS 2.0和Atom格式
- 统一的数据结构转换
- 字段标准化处理
### 3. 源配置系统 (`shared/pre-sources.ts`)
- 集中管理所有RSS源配置
- 支持源属性:名称、颜色、更新间隔、描述等
- 自动生成sources.json配置文件
## 添加RSS源的标准流程
### 步骤1:测试RSS源可用性
```bash
# 测试RSS源是否可访问
curl -s -H "User-Agent: Mozilla/5.0" https://example.com/rss.xml | head -10
# 检查格式
npx tsx -e "
import { fetchRSS } from './utils/rss-fetch'
const result = await fetchRSS('https://example.com/rss.xml')
console.log('格式:', result.rss ? 'RSS' : result.feed ? 'Atom' : 'Unknown')
console.log('条目数:', result.rss?.channel?.item?.length || result.feed?.entry?.length)
"
### 步骤2:创建源文件
在 `server/sources/` 目录下创建新的TypeScript文件:
```typescript
import type { NewsItem } from "@shared/types"
import { fetchRSS } from "../utils/rss-fetch"
// RSS 2.0 格式示例
export default defineSource(async () => {
try {
const result = await fetchRSS("https://example.com/rss.xml")
if (!result.rss?.channel?.item) {
console.error("[EXAMPLE] No items found in RSS")
throw new Error("Cannot fetch rss data")
}
const items = Array.isArray(result.rss.channel.item)
? result.rss.channel.item
: [result.rss.channel.item]
return items.map(item => ({
id: item.guid || item.link || `example-${Date.now()}`,
title: item.title || "",
url: item.link || "",
description: item.description || "",
pubDate: item.pubDate ? new Date(item.pubDate) : undefined,
extra: {
author: item.author || item["dc:creator"] || "",
}
}))
} catch (error) {
console.error("[EXAMPLE] Error:", error)
throw new Error("Cannot fetch rss data")
}
})
Atom格式示例:
```typescript
// Atom 格式示例
if (result.feed?.entry) {
const entries = Array.isArray(result.feed.entry)
? result.feed.entry
: [result.feed.entry]
return entries.map(entry => ({
id: entry.id || entry.link || `example-${Date.now()}`,
title: entry.title || "",
url: entry.link || "",
description: entry.summary || entry.content || "",
pubDate: entry.updated ? new Date(entry.updated) : undefined,
extra: {
author: entry.author?.name || "作者名",
}
}))
}
### 步骤3:配置源属性
在 `shared/pre-sources.ts` 中添加源配置:
```typescript
"example": {
name: "示例网站",
column: "tech", // 分类:tech, world, china, etc.
color: "blue", // 颜色:blue, red, green, purple, etc.
title: "示例标题",
type: "realtime", // 类型:realtime, blog, tech, hottest
interval: Time.Default, // 更新间隔
home: "https://example.com/",
desc: "示例网站描述",
},
时间间隔选项:
- `Time.Test`: 1分钟(测试用)
- `Time.Realtime`: 2分钟(实时新闻)
- `Time.Fast`: 5分钟(快讯)
- `Time.Default`: 10分钟(默认)
- `Time.Common`: 30分钟(常规)
- `Time.Slow`: 60分钟(慢更新)
### 步骤4:重启服务器
```bash
npm run dev
### 步骤5:测试API
```bash
curl -s "http://localhost:5177/api/s?id=example" | jq '.items[0]'
## 常见问题与解决方案
### 1. Cloudflare保护(403错误)
**症状**:返回"Just a moment..."页面或403错误
**解决方案**:
- 寻找替代RSS源(如Feedburner、RSSHub等)
- 使用RSS代理服务
- 检查是否有官方API
**成功案例**:
- 阮一峰博客:原URL被Cloudflare保护,改用 `https://feeds.feedburner.com/ruanyifeng`
- Linux.do:尝试多个代理服务,目前使用RSSProxy
### 2. RSS格式解析错误
**症状**:解析失败或数据结构不正确
**解决方案**:
- 使用测试脚本确认RSS格式(RSS 2.0 vs Atom)
- 检查字段映射(如Atom的entry vs RSS的item)
- 处理CDATA内容
### 3. 编码问题
**症状**:中文乱码
**解决方案**:
- fast-xml-parser自动处理UTF-8编码
- 确保源文件使用UTF-8编码
### 4. 重定向问题
**症状**:获取的内容不是预期的RSS
**解决方案**:
- 检查最终URL(可能有多次重定向)
- 使用curl -L参数跟踪重定向
## 高级技巧
### 1. 处理复杂XML结构
```typescript
// 处理带命名空间的XML
const xml = new XMLParser({
attributeNamePrefix: "",
textNodeName: "$text",
ignoreAttributes: false,
trimValues: true,
// 处理命名空间
parseTagValue: true,
parseAttributeValue: true,
})
### 2. 自定义字段映射
```typescript
return items.map(item => ({
id: item.guid?.$text || item.link || `custom-${Date.now()}`,
title: item.title?.$text || item.title || "",
url: item.link?.$text || item.link || "",
description: item.description?.$text || item.description || "",
pubDate: item.pubDate || item.updated || item.date,
extra: {
author: item.author || item["dc:creator"] || item.creator || "",
category: item.category || "",
}
}))
### 3. 错误处理增强
```typescript
try {
const result = await fetchRSS(url)
// 验证数据结构
if (!result.rss?.channel?.item && !result.feed?.entry) {
throw new Error("Invalid RSS structure")
}
} catch (error) {
if (error.message.includes('403')) {
console.error("[SOURCE] Access denied - possible Cloudflare protection")
} else if (error.message.includes('404')) {
console.error("[SOURCE] RSS feed not found")
}
throw new Error("Cannot fetch rss data")
}
|