Skip to main content

tdm_server_rust/entity/
rss.rs

1//! RSS 生成用数据结构 (RSS Feed Entities)
2//!
3//! 定义 RSS 2.0 订阅源生成所需的全部数据结构。
4//!
5//! ## RSS 体系概览
6//!
7//! 提灯喵网站通过 RSS 2.0 对外提供以下订阅源:
8//!
9//! | RSS 文件 | 内容 | 来源查询 |
10//! |----------|------|----------|
11//! | `rss.xml` | 最新开坑漫画 | `get_manga_rss` |
12//! | `rssUpdated.xml` | 最近更新漫画 | `get_manga_updated_rss` |
13//! | `rssEpisode.xml` | 最新话数发布 | `get_episode_rss` |
14//! | `rssEpisode_{post}.xml` | 各岗位工作提醒 | `get_work_reminder_rss` |
15//!
16//! ## 数据流
17//!
18//! ```text
19//! 数据库查询 → RssXxxRow → RssService 格式化 → RssItem → RSS XML 文件
20//! ```
21//!
22//! `RssXxxRow` 是数据库查询的中间行结构,
23//! [`RssItem`] 是统一的 RSS 条目格式,
24//! 最终由 `RssService` 序列化为 XML 写入 `folder.base` 目录。
25
26use chrono::{DateTime, Utc};
27use serde::Serialize;
28
29/// RSS 2.0 统一条目
30///
31/// 所有 RSS 订阅源的条目均转换为此格式后再序列化为 XML。
32/// 包含 RSS 2.0 规范要求的全部可选字段。
33///
34/// ## 字段说明
35///
36/// | 字段 | RSS 元素 | 说明 |
37/// |------|----------|------|
38/// | `title` | `<title>` | 条目标题(漫画译名 或 话数标签) |
39/// | `link` | `<link>` | 条目链接(站内 URL) |
40/// | `author` | `<author>` | 作者信息(翻译组员名 或 原作作者名) |
41/// | `pub_date` | `<pubDate>` | RFC 2822 格式发布时间 |
42/// | `category` | `<category>` | 分类描述(长短篇 + 连载状态) |
43/// | `description` | `<description>` | HTML 正文(CDATA 包裹) |
44/// | `enclosure_url` | `<enclosure url="...">` | 封面图片 URL |
45/// | `enclosure_type` | `<enclosure type="...">` | 封面 MIME 类型(通常 `image/jpeg`) |
46/// | `guid` | `<guid>` | 唯一标识(漫画 ID 或话数 ID) |
47#[derive(Debug, Clone)]
48pub struct RssItem {
49    /// RSS 条目标题
50    pub title: String,
51    /// RSS 条目链接(完整 URL)
52    pub link: String,
53    /// 作者信息(组员名或原作作者名)
54    pub author: String,
55    /// 发布时间(中文格式,如"2024年1月15日20点30分")
56    pub pub_date: String,
57    /// 分类描述(长短篇 + 连载状态)
58    pub category: String,
59    /// HTML 正文描述(CDATA 包裹)
60    pub description: String,
61    /// 封面图片 URL
62    pub enclosure_url: String,
63    /// 封面图片 MIME 类型
64    pub enclosure_type: String,
65    /// 全局唯一标识符
66    pub guid: String,
67}
68
69/// 漫画 RSS 查询行
70///
71/// 来自 `get_manga_rss` 和 `get_manga_updated_rss` 查询的中间结果。
72/// 包含漫画基本信息 + 作者信息,用于生成 `rss.xml` 和 `rssUpdated.xml`。
73///
74/// ## 查询来源
75///
76/// - [`MangaRepository::get_manga_rss`](crate::repository::manga_repo::MangaRepository) — 最新开坑漫画(按 `setupTime` 降序取 2 条)
77/// - [`MangaRepository::get_manga_updated_rss`](crate::repository::manga_repo::MangaRepository) — 最近更新漫画(按 `updateTime` 降序取 3 条)
78///
79/// ## 对应 Java 查询
80///
81/// Java `MangaMapper.getMangaRss`。
82#[derive(Debug, Clone, Serialize, Default)]
83#[serde(rename_all = "camelCase")]
84pub struct RssMangaRow {
85    /// 漫画 ID,序列化为 `"Id"`
86    #[serde(rename = "Id")]
87    pub id: i32,
88    /// 漫画译名
89    pub manga_tran_name: Option<String>,
90    /// 漫画原名
91    pub manga_ori_name: Option<String>,
92    /// 漫画分类
93    pub category: Option<i16>,
94    /// 连载状态
95    pub manga_status: Option<i16>,
96    /// 封面图片路径
97    pub image: Option<String>,
98    /// 开坑时间
99    pub setup_time: Option<DateTime<Utc>>,
100    /// 最近更新时间
101    pub update_time: Option<DateTime<Utc>>,
102    /// 原作作者 ID
103    pub author_id: Option<i32>,
104    /// 原作作者名
105    pub author_name: Option<String>,
106    /// 作画作者 ID
107    pub author_id2: Option<i32>,
108    /// 作画作者名
109    pub author_name2: Option<String>,
110}
111
112/// 话数 RSS 查询行
113///
114/// 来自 `get_episode_rss` 查询的中间结果。
115/// 包含话数信息 + 关联漫画信息 + 图源/翻译组员信息。
116/// 用于生成 `rssEpisode.xml`。
117///
118/// ## 查询来源
119///
120/// [`MangaRepository::get_episode_rss`](crate::repository::manga_repo::MangaRepository) — 最新话数发布(按 ID 降序取 5 条)。
121///
122/// ## 对应 Java 查询
123///
124/// Java `MangaMapper.getEpisodeRss`。
125#[derive(Debug, Clone, Serialize, Default)]
126#[serde(rename_all = "camelCase")]
127pub struct EpisodeRssRow {
128    /// 话数 ID,序列化为 `"Id"`
129    #[serde(rename = "Id")]
130    pub id: i32,
131    /// 漫画 ID
132    pub manga_id: i32,
133    /// 话数标签(如"第1话")
134    pub manga_episode: Option<String>,
135    /// 话数标题
136    pub manga_episode_name: Option<String>,
137    /// 话数设立时间
138    pub setup_time: Option<DateTime<Utc>>,
139    /// 漫画译名
140    pub manga_name: Option<String>,
141    /// 漫画原名
142    pub manga_ori_name: Option<String>,
143    /// 漫画分类
144    pub category: Option<i16>,
145    /// 漫画连载状态
146    pub manga_status: Option<i16>,
147    /// 漫画开坑时间
148    pub manga_setup_time: Option<DateTime<Utc>>,
149    /// 封面图片路径
150    pub image: Option<String>,
151    /// 图源组员名
152    pub provider_name: Option<String>,
153    /// 翻译组员名
154    pub translator_name: Option<String>,
155    /// 翻译职阶
156    pub intern: Option<i16>,
157    /// 翻译邮箱
158    pub email: Option<String>,
159    /// 发布链接
160    pub publish_link: Option<String>,
161}
162
163/// 交稿提醒 RSS 查询行
164///
165/// 来自 `get_work_reminder_rss` 查询的中间结果。
166/// 记录某岗位组员交稿后,下一工序组员的提醒信息。
167///
168/// ## 字段含义
169///
170/// `my_name` 为当前交稿岗位(如 "translator" 完成翻译,提醒校对接稿)。
171/// `username_now`/`intern_now`/`email_now` 为交稿组员信息。
172/// `username`/`intern`/`email` 为下一工序组员信息。
173///
174/// ## 查询来源
175///
176/// [`MangaRepository::get_work_reminder_rss`](crate::repository::manga_repo::MangaRepository) — 按岗位名查询最新 5 条交稿提醒。
177///
178/// ## 对应 Java 查询
179///
180/// Java `MangaMapper.getWorkReminderRss`。
181#[derive(Debug, Clone, Default)]
182pub struct WorkReminderRssRow {
183    /// 话数 ID
184    pub episode_id: i32,
185    /// 漫画 ID
186    pub manga_id: i32,
187    /// 话数标签
188    pub manga_episode: Option<String>,
189    /// 话数标题
190    pub manga_episode_name: Option<String>,
191    /// 话数设立时间
192    pub setup_time: Option<DateTime<Utc>>,
193    /// 漫画译名
194    pub manga_name: Option<String>,
195    /// 漫画原名
196    pub manga_ori_name: Option<String>,
197    /// 漫画分类
198    pub category: Option<i16>,
199    /// 漫画连载状态
200    pub manga_status: Option<i16>,
201    /// 漫画开坑时间
202    pub manga_setup_time: Option<DateTime<Utc>>,
203    /// 封面图片路径
204    pub image: Option<String>,
205    /// 当前交稿岗位英文名(translator / proofreader / letterer / timer)
206    pub my_name: String,
207    /// 交稿组员名
208    pub username_now: Option<String>,
209    /// 交稿组员职阶
210    pub intern_now: Option<i16>,
211    /// 交稿组员邮箱
212    pub email_now: Option<String>,
213    /// 下一工序组员名
214    pub username: Option<String>,
215    /// 下一工序组员职阶
216    pub intern: Option<i16>,
217    /// 下一工序组员邮箱
218    pub email: Option<String>,
219}
220
221/// 三月未交稿组员 RSS 查询行
222///
223/// 查询超过三个月未交稿的组员信息,用于生成提醒 RSS。
224///
225/// ## 查询来源
226///
227/// [`MemberRepository::get_member_reminder_rss`](crate::repository::member_repo::MemberRepository) — 筛选 `lastSubmitTime <= NOW() - 3 MONTH`。
228#[derive(Debug, Clone, Default)]
229pub struct MemberReminderRssRow {
230    /// 组员 ID
231    pub id: i32,
232    /// 组员用户名
233    pub username: Option<String>,
234    /// 组员职阶
235    pub intern: i16,
236    /// 组员邮箱
237    pub email: Option<String>,
238    /// 最近一次交稿时间
239    pub last_submit_time: Option<DateTime<Utc>>,
240}
241
242/// 岗位 RSS 输出配置
243///
244/// 定义每个岗位对应的 RSS XML 文件名和查询参数。
245/// 用于 `RSS_POST_CONFIGS` 常量数组,驱动各岗位 RSS 文件的批量生成。
246///
247/// ## 字段说明
248///
249/// | 字段 | 说明 |
250/// |------|------|
251/// | `post_name` | 岗位英文名,对应 `workreminder.myName` 列值 |
252/// | `file_name` | 输出的 RSS XML 文件名 |
253pub struct RssPostConfig {
254    /// 岗位英文名(translator / proofreader / letterer / timer / reviewer)
255    pub post_name: &'static str,
256    /// 输出的 RSS XML 文件名
257    pub file_name: &'static str,
258}
259
260/// 全部岗位 RSS 输出配置
261///
262/// 包含翻译、校对、嵌字、审稿、时轴五个岗位的 RSS 文件配置。
263/// 用于 `RssService` 中批量生成各岗位的工作提醒 RSS XML。
264///
265/// ## 使用方式
266///
267/// ```rust,ignore
268/// for config in RSS_POST_CONFIGS {
269///     let rows = repo.get_work_reminder_rss(config.post_name).await?;
270///     write_rss_file(config.file_name, rows).await?;
271/// }
272/// ```
273pub const RSS_POST_CONFIGS: &[RssPostConfig] = &[
274    RssPostConfig {
275        post_name: "translator",
276        file_name: "rssEpisode_translator.xml",
277    },
278    RssPostConfig {
279        post_name: "proofreader",
280        file_name: "rssEpisode_proofreader.xml",
281    },
282    RssPostConfig {
283        post_name: "letterer",
284        file_name: "rssEpisode_letterer.xml",
285    },
286    RssPostConfig {
287        post_name: "reviewer",
288        file_name: "rssEpisode_reviewer.xml",
289    },
290    RssPostConfig {
291        post_name: "timer",
292        file_name: "rssEpisode_timer.xml",
293    },
294];