feat: 新增书籍导入和列表接口,完善数据库初始化
1. 新增encoding_rs、regex、chardetng依赖用于文本编码处理 2. 重构数据库初始化逻辑,适配tauri应用数据目录 3. 新增ApiResponse统一返回格式和书籍、章节数据模型 4. 重写import_book和book_list命令实现基础导入和列表功能
This commit is contained in:
parent
5c1be1cdb0
commit
efae900fcf
|
|
@ -488,6 +488,17 @@ dependencies = [
|
|||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chardetng"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13de944a44b5064ee5d3a5ceccc49a41bfec50f2580e66f82e87703acdb88b53"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"encoding_rs",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.45"
|
||||
|
|
@ -1024,6 +1035,15 @@ version = "1.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.1"
|
||||
|
|
@ -3829,7 +3849,10 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
|||
name = "stealthreader"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chardetng",
|
||||
"chrono",
|
||||
"encoding_rs",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
|
|
|
|||
|
|
@ -36,3 +36,6 @@ sqlx = { version = "0.9.0", features = [
|
|||
] }
|
||||
chrono = "0.4.45"
|
||||
tauri-plugin-dialog = "2.7.2"
|
||||
encoding_rs = "0.8.35"
|
||||
regex = "1.13.1"
|
||||
chardetng = "1.0.0"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,25 @@
|
|||
use tauri_helper::auto_collect_command;
|
||||
|
||||
use crate::models::ApiResponse;
|
||||
|
||||
/**
|
||||
* 导入书籍
|
||||
* @param path 书籍文件路径
|
||||
* @returns 导入结果
|
||||
*/
|
||||
#[tauri::command]
|
||||
#[auto_collect_command]
|
||||
pub async fn import_book() -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
pub async fn import_book(path: String) -> Result<ApiResponse<()>, String> {
|
||||
let content = std::fs::read_to_string(&path).map_err(|e| e.to_string());
|
||||
Ok(ApiResponse::success(()))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取书籍列表
|
||||
* @returns 书籍列表
|
||||
*/
|
||||
#[tauri::command]
|
||||
#[auto_collect_command]
|
||||
pub async fn book_list() -> Result<ApiResponse<Vec<()>>, String> {
|
||||
Ok(ApiResponse::success(Vec::new()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ pub fn run() {
|
|||
.plugin(tauri_plugin_dialog::init())
|
||||
.setup(|app| {
|
||||
let pool = tauri::async_runtime::block_on(async {
|
||||
init_pool().await.expect("sqlite数据库连接池初始化失败!")
|
||||
init_pool(app.handle()).await.expect("sqlite数据库连接池初始化失败!")
|
||||
});
|
||||
app.manage(pool);
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -45,3 +45,36 @@ impl<T> ApiResponse<T> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct BookInfo {
|
||||
pub id: i64,
|
||||
pub title: String,
|
||||
pub author: String,
|
||||
pub cover: String,
|
||||
pub total_chapters: i32,
|
||||
pub total_chars: i32,
|
||||
pub introduction: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Chapter {
|
||||
pub id: i64,
|
||||
pub number: i32,
|
||||
pub title: String,
|
||||
pub content: String,
|
||||
pub char_count: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Book {
|
||||
pub id: Option<i64>,
|
||||
pub title: String,
|
||||
pub author: String,
|
||||
pub cover: String,
|
||||
pub introduction: String,
|
||||
pub file_path: String,
|
||||
pub total_chapters: i32,
|
||||
pub total_chars: i32,
|
||||
pub chapters: Vec<Chapter>,
|
||||
}
|
||||
|
|
@ -1,10 +1,50 @@
|
|||
use sqlx::pool::PoolOptions;
|
||||
use sqlx::sqlite::{SqliteConnectOptions, SqlitePool, SqlitePoolOptions};
|
||||
use sqlx::sqlite::{SqlitePool, SqlitePoolOptions};
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
pub async fn init_pool() -> Result<SqlitePool, sqlx::Error> {
|
||||
SqlitePoolOptions::new()
|
||||
pub async fn init_pool(app: &AppHandle) -> Result<SqlitePool, sqlx::Error> {
|
||||
// 数据库文件路径
|
||||
let app_dir = app.path().app_data_dir().expect("无法获取APP数据目录");
|
||||
std::fs::create_dir_all(&app_dir).unwrap();
|
||||
let db_path = app_dir.join("reader.db");
|
||||
let db_url = format!("sqlite:{}", db_path.to_str().unwrap());
|
||||
// 初始化数据库连接池
|
||||
let pool = SqlitePoolOptions::new()
|
||||
.max_connections(5)
|
||||
.min_connections(1)
|
||||
.connect("sqlite::memory:")
|
||||
.await
|
||||
.connect(&db_url)
|
||||
.await?;
|
||||
sqlx::query(
|
||||
"
|
||||
CREATE TABLE IF NOT EXISTS books (
|
||||
id INTEGER,
|
||||
title TEXT DEFAULT '',
|
||||
author TEXT DEFAULT '',
|
||||
cover TEXT DEFAULT '',
|
||||
file_path TEXT DEFAULT '',
|
||||
total_chapters INTEGER DEFAULT 0,
|
||||
total_chars INTEGER DEFAULT 0,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
last_read_chapter_id INTEGER DEFAULT 0,
|
||||
last_read_position INTEGER DEFAULT 0,
|
||||
last_read_time TIMESTAMP,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS chapters (
|
||||
id INTEGER,
|
||||
book_id INTEGER NOT NULL,
|
||||
chapter_number INTEGER NOT NULL,
|
||||
title TEXT NOT NULL DEFAULT '',
|
||||
content TEXT NOT NULL,
|
||||
total_chars INTEGER DEFAULT 0,
|
||||
FOREIGN KEY (book_id) REFERENCES books ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_chapters_book
|
||||
ON chapters(book_id, chapter_number);
|
||||
",
|
||||
)
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
Ok(pool)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue