Skip to main content

tdm_server_rust/repository/
magazine_repo.rs

1//! 杂志数据访问层 (Magazine Repository)
2//!
3//! 封装 `magazinetb` 表的增删改查。
4
5use crate::entity::{magazine::Magazine, manga::Manga};
6use sqlx::{MySql, Pool, Row};
7
8/// 杂志完整字段(含种类、价格)
9#[derive(Debug, Clone)]
10pub struct MagazineRow {
11    /// 杂志 ID
12    pub id: Option<i32>,
13    /// 杂志名
14    pub magazine_name: Option<String>,
15    /// 种类
16    pub type_: Option<i16>,
17    /// 更新时间字符串
18    pub update_time: Option<String>,
19    /// 每期价格
20    pub price: Option<i32>,
21}
22
23/// 杂志仓储
24pub struct MagazineRepository {
25    /// 连接池
26    pool: Pool<MySql>,
27}
28
29impl MagazineRepository {
30    /// 构造仓储
31    pub fn new(pool: Pool<MySql>) -> Self {
32        Self { pool }
33    }
34
35    /// 条件查询杂志列表
36    #[tracing::instrument(skip_all, level = "debug")]
37    pub async fn get_magazines(
38        &self,
39        magazine_id: Option<i16>,
40        magazine_name: Option<&str>,
41    ) -> crate::error::ApiResult<Vec<MagazineRow>> {
42        let mut sql = String::from("SELECT Id, magazineName, type, updateTime, price FROM magazine WHERE 1=1");
43        if magazine_id.is_some() {
44            sql.push_str(" AND Id = ?");
45        }
46        if magazine_name.is_some() {
47            sql.push_str(" AND magazineName = ?");
48        }
49        let mut q = sqlx::query(&sql);
50        if let Some(id) = magazine_id {
51            q = q.bind(id);
52        }
53        if let Some(name) = magazine_name {
54            q = q.bind(name);
55        }
56        let rows = q.fetch_all(&self.pool).await?;
57        Ok(rows.into_iter().map(|r| row_to_magazine_row(&r)).collect())
58    }
59
60    /// 按 ID 删除杂志
61    #[tracing::instrument(skip_all, level = "debug")]
62    pub async fn delete_magazine_by_id(&self, id: i32) -> crate::error::ApiResult<()> {
63        sqlx::query("DELETE FROM magazine WHERE Id = ?")
64            .bind(id)
65            .execute(&self.pool)
66            .await?;
67        Ok(())
68    }
69
70    /// 新增杂志
71    #[tracing::instrument(skip_all, level = "debug")]
72    pub async fn insert_magazine(&self, row: &MagazineRow) -> crate::error::ApiResult<i32> {
73        let r = sqlx::query(
74            "INSERT INTO magazine(magazineName, type, updateTime, price) VALUES (?, ?, ?, ?)",
75        )
76        .bind(&row.magazine_name)
77        .bind(row.type_)
78        .bind(&row.update_time)
79        .bind(row.price)
80        .execute(&self.pool)
81        .await?;
82        Ok(r.last_insert_id() as i32)
83    }
84
85    /// 按 ID 查询杂志
86    #[tracing::instrument(skip_all, level = "debug")]
87    pub async fn get_magazine_by_id(&self, id: i32) -> crate::error::ApiResult<Option<MagazineRow>> {
88        let row = sqlx::query(
89            "SELECT Id, magazineName, type, updateTime, price FROM magazine WHERE Id = ?",
90        )
91        .bind(id)
92        .fetch_optional(&self.pool)
93        .await?;
94        Ok(row.map(|r| row_to_magazine_row(&r)))
95    }
96
97    /// 更新杂志
98    #[tracing::instrument(skip_all, level = "debug")]
99    pub async fn update_magazine(&self, row: &MagazineRow) -> crate::error::ApiResult<()> {
100        sqlx::query(
101            "UPDATE magazine SET type = COALESCE(?, type), magazineName = COALESCE(?, magazineName), \
102             updateTime = COALESCE(?, updateTime), price = COALESCE(?, price) WHERE Id = ?",
103        )
104        .bind(row.type_)
105        .bind(&row.magazine_name)
106        .bind(&row.update_time)
107        .bind(row.price)
108        .bind(row.id)
109        .execute(&self.pool)
110        .await?;
111        Ok(())
112    }
113
114    /// 按名称检测杂志是否已存在
115    #[tracing::instrument(skip_all, level = "debug")]
116    pub async fn test_magazine_name(&self, magazine_name: &str) -> crate::error::ApiResult<Option<MagazineRow>> {
117        let row = sqlx::query(
118            "SELECT Id, magazineName, type, updateTime, price FROM magazine WHERE magazineName = ?",
119        )
120        .bind(magazine_name)
121        .fetch_optional(&self.pool)
122        .await?;
123        Ok(row.map(|r| row_to_magazine_row(&r)))
124    }
125
126    /// 删除前检查是否绑定漫画
127    #[tracing::instrument(skip_all, level = "debug")]
128    pub async fn get_related_magazine(&self, id: i32) -> crate::error::ApiResult<bool> {
129        let row = sqlx::query("SELECT magazineId FROM mangamagazine WHERE magazineId = ? LIMIT 1")
130            .bind(id)
131            .fetch_optional(&self.pool)
132            .await?;
133        Ok(row.is_some())
134    }
135
136    /// 获取最大杂志 ID
137    #[tracing::instrument(skip_all, level = "debug")]
138    pub async fn get_max_magazine_id(&self) -> crate::error::ApiResult<Option<i32>> {
139        let row = sqlx::query("SELECT MAX(Id) AS maxId FROM magazine")
140            .fetch_one(&self.pool)
141            .await?;
142        Ok(row.try_get("maxId").ok())
143    }
144
145    /// 无条件查询全部杂志
146    #[tracing::instrument(skip_all, level = "debug")]
147    pub async fn get_manga_magazine_list(&self) -> crate::error::ApiResult<Vec<MagazineRow>> {
148        let rows = sqlx::query("SELECT Id, magazineName, type, updateTime, price FROM magazine")
149            .fetch_all(&self.pool)
150            .await?;
151        Ok(rows.into_iter().map(|r| row_to_magazine_row(&r)).collect())
152    }
153
154    /// 查询杂志关联的全部漫画
155    #[tracing::instrument(skip_all, level = "debug")]
156    pub async fn get_magazine_manga(&self, id: i32) -> crate::error::ApiResult<Vec<Manga>> {
157        let rows = sqlx::query(
158            "SELECT DISTINCT m.Id, m.mangaTranName, m.mangaOriName, m.category, m.mangaStatus, \
159             m.image AS imgUrl, m.updateTime \
160             FROM mangatb m \
161             LEFT JOIN mangamagazine mm ON m.Id = mm.mangaId \
162             LEFT JOIN magazine maga ON mm.magazineId = maga.Id \
163             WHERE maga.Id = ? \
164             GROUP BY m.Id, m.updateTime \
165             ORDER BY m.updateTime DESC",
166        )
167        .bind(id)
168        .fetch_all(&self.pool)
169        .await?;
170        Ok(rows.into_iter().map(|r| row_to_manga(&r)).collect())
171    }
172
173    /// 将完整行转为实体 Magazine(仅 ID 与名称)
174    pub fn to_entity(row: &MagazineRow) -> Magazine {
175        Magazine {
176            id: row.id,
177            magazine_name: row.magazine_name.clone(),
178            r#type: row.type_,
179            update_time: row.update_time.clone(),
180            price: row.price,
181        }
182    }
183}
184
185/// 行映射为杂志完整字段
186fn row_to_magazine_row(row: &sqlx::mysql::MySqlRow) -> MagazineRow {
187    MagazineRow {
188        id: row.try_get("Id").ok(),
189        magazine_name: row.try_get("magazineName").ok(),
190        type_: row.try_get("type").ok(),
191        update_time: row.try_get("updateTime").ok(),
192        price: row.try_get("price").ok(),
193    }
194}
195
196/// 行映射为漫画
197fn row_to_manga(row: &sqlx::mysql::MySqlRow) -> Manga {
198    use chrono::{DateTime, Utc};
199    Manga {
200        id: row.try_get("Id").ok(),
201        manga_tran_name: row.try_get("mangaTranName").ok(),
202        manga_ori_name: row.try_get("mangaOriName").ok(),
203        category: row.try_get("category").ok(),
204        manga_status: row.try_get("mangaStatus").ok(),
205        img_url: row.try_get("imgUrl").ok(),
206        link: None,
207        introduction: None,
208        update_time: row.try_get::<DateTime<Utc>, _>("updateTime").ok(),
209    }
210}