fix: 修复数据库迁移问题

- 将表创建和索引创建逻辑从仓储初始化方法移到迁移系统
- 移除 outfit_image_repository.rs 中的 init_tables 表创建逻辑
- 确保所有数据库结构变更通过迁移系统统一管理
- 修复 comfyui_prompt_id 索引创建时机问题

这样可以避免在旧数据库上创建索引时找不到字段的错误。
This commit is contained in:
imeepos 2025-07-31 15:31:17 +08:00
parent 4b20f69560
commit 676effdab4
6 changed files with 2 additions and 116 deletions

View File

@ -82,13 +82,11 @@ impl ModelService {
pub fn get_all_models( pub fn get_all_models(
repository: &ModelRepository, repository: &ModelRepository,
) -> Result<Vec<Model>> { ) -> Result<Vec<Model>> {
println!("ModelService::get_all_models 开始执行");
let result = repository.get_all() let result = repository.get_all()
.map_err(|e| { .map_err(|e| {
println!("repository.get_all() 失败: {}", e); println!("repository.get_all() 失败: {}", e);
anyhow!("获取模特列表失败: {}", e) anyhow!("获取模特列表失败: {}", e)
}); });
println!("ModelService::get_all_models 执行完成,结果: {:?}", result.is_ok());
result result
} }

View File

@ -171,7 +171,6 @@ impl ModelRepository {
/// 获取所有模特 /// 获取所有模特
pub fn get_all(&self) -> Result<Vec<Model>> { pub fn get_all(&self) -> Result<Vec<Model>> {
println!("ModelRepository::get_all 开始执行");
// 首先获取所有模特的基本信息 // 首先获取所有模特的基本信息
let mut models = { let mut models = {
@ -202,11 +201,8 @@ impl ModelRepository {
let mut models = Vec::new(); let mut models = Vec::new();
for (index, model) in model_iter.enumerate() { for (index, model) in model_iter.enumerate() {
println!("处理第 {} 个模特记录", index + 1);
println!("开始解析模特数据...");
let model = match model { let model = match model {
Ok(m) => { Ok(m) => {
println!("模特数据解析成功: {}", m.name);
m m
} }
Err(e) => { Err(e) => {
@ -215,17 +211,14 @@ impl ModelRepository {
} }
}; };
models.push(model); models.push(model);
println!("{} 个模特记录处理完成", index + 1);
} }
models models
}; // 释放数据库连接锁 }; // 释放数据库连接锁
// 然后为每个模特加载照片信息(避免嵌套锁) // 然后为每个模特加载照片信息(避免嵌套锁)
for model in &mut models { for model in &mut models {
println!("开始加载模特 {} 的照片信息", model.id);
model.photos = match self.get_photos(&model.id) { model.photos = match self.get_photos(&model.id) {
Ok(photos) => { Ok(photos) => {
println!("照片信息加载成功,共 {} 张照片", photos.len());
photos photos
} }
Err(e) => { Err(e) => {
@ -235,11 +228,6 @@ impl ModelRepository {
} }
}; };
} }
println!(
"ModelRepository::get_all 执行完成,返回 {} 个模特",
models.len()
);
Ok(models) Ok(models)
} }
@ -517,7 +505,6 @@ impl ModelRepository {
description, tags, is_cover, created_at description, tags, is_cover, created_at
FROM model_photos WHERE model_id = ?1 ORDER BY created_at DESC", FROM model_photos WHERE model_id = ?1 ORDER BY created_at DESC",
)?; )?;
println!("get_photos SQL 语句准备成功");
let photo_iter = stmt.query_map([model_id], |row| self.row_to_photo(row))?; let photo_iter = stmt.query_map([model_id], |row| self.row_to_photo(row))?;
@ -732,7 +719,6 @@ impl ModelRepository {
println!("row_to_photo photo_type 解析失败: {}", e); println!("row_to_photo photo_type 解析失败: {}", e);
rusqlite::Error::FromSqlConversionFailure(0, rusqlite::types::Type::Text, Box::new(e)) rusqlite::Error::FromSqlConversionFailure(0, rusqlite::types::Type::Text, Box::new(e))
})?; })?;
println!("row_to_photo photo_type 解析成功");
let tags_str: String = row.get("tags")?; let tags_str: String = row.get("tags")?;
let tags: Vec<String> = serde_json::from_str(&tags_str).map_err(|e| { let tags: Vec<String> = serde_json::from_str(&tags_str).map_err(|e| {

View File

@ -23,103 +23,8 @@ impl OutfitImageRepository {
/// 初始化数据库表(强制使用连接池) /// 初始化数据库表(强制使用连接池)
pub fn init_tables(&self) -> Result<()> { pub fn init_tables(&self) -> Result<()> {
// 表创建和索引创建现在通过迁移系统处理
// 🚨 强制使用连接池,避免死锁 // 这个方法保留用于未来可能的初始化逻辑
if !self.database.has_pool() {
return Err(anyhow!("连接池未启用,无法安全执行数据库操作"));
}
let pooled_conn = self.database.acquire_from_pool()
.map_err(|e| anyhow!("获取连接池连接失败: {}", e))?;
// 创建穿搭图片生成记录表
pooled_conn.execute(
r#"
CREATE TABLE IF NOT EXISTS outfit_image_records (
id TEXT PRIMARY KEY,
model_id TEXT NOT NULL,
model_image_id TEXT NOT NULL,
generation_prompt TEXT,
status TEXT NOT NULL DEFAULT 'pending',
progress REAL NOT NULL DEFAULT 0.0,
result_urls TEXT NOT NULL DEFAULT '[]',
error_message TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
started_at DATETIME,
completed_at DATETIME,
duration_ms INTEGER,
comfyui_prompt_id TEXT,
FOREIGN KEY (model_id) REFERENCES models (id) ON DELETE CASCADE,
FOREIGN KEY (model_image_id) REFERENCES model_photos (id) ON DELETE CASCADE
)
"#,
[],
).map_err(|e| anyhow!("创建穿搭图片记录表失败: {}", e))?;
// 创建商品图片表
pooled_conn.execute(
r#"
CREATE TABLE IF NOT EXISTS product_images (
id TEXT PRIMARY KEY,
outfit_record_id TEXT NOT NULL,
file_path TEXT NOT NULL,
file_name TEXT NOT NULL,
file_size INTEGER NOT NULL,
upload_url TEXT,
description TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (outfit_record_id) REFERENCES outfit_image_records (id) ON DELETE CASCADE
)
"#,
[],
).map_err(|e| anyhow!("创建商品图片表失败: {}", e))?;
// 创建穿搭图片表
pooled_conn.execute(
r#"
CREATE TABLE IF NOT EXISTS outfit_images (
id TEXT PRIMARY KEY,
outfit_record_id TEXT NOT NULL,
image_url TEXT NOT NULL,
local_path TEXT,
image_index INTEGER NOT NULL,
description TEXT,
tags TEXT,
is_favorite BOOLEAN DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (outfit_record_id) REFERENCES outfit_image_records (id) ON DELETE CASCADE
)
"#,
[],
).map_err(|e| anyhow!("创建穿搭图片表失败: {}", e))?;
// 创建索引
pooled_conn.execute(
"CREATE INDEX IF NOT EXISTS idx_outfit_image_records_model_id ON outfit_image_records (model_id)",
[],
).map_err(|e| anyhow!("创建索引失败: {}", e))?;
pooled_conn.execute(
"CREATE INDEX IF NOT EXISTS idx_outfit_image_records_status ON outfit_image_records (status)",
[],
).map_err(|e| anyhow!("创建索引失败: {}", e))?;
pooled_conn.execute(
"CREATE INDEX IF NOT EXISTS idx_outfit_image_records_comfyui_prompt_id ON outfit_image_records (comfyui_prompt_id)",
[],
).map_err(|e| anyhow!("创建索引失败: {}", e))?;
pooled_conn.execute(
"CREATE INDEX IF NOT EXISTS idx_product_images_outfit_record_id ON product_images (outfit_record_id)",
[],
).map_err(|e| anyhow!("创建索引失败: {}", e))?;
pooled_conn.execute(
"CREATE INDEX IF NOT EXISTS idx_outfit_images_outfit_record_id ON outfit_images (outfit_record_id)",
[],
).map_err(|e| anyhow!("创建索引失败: {}", e))?;
Ok(()) Ok(())
} }

View File

@ -295,7 +295,6 @@ impl Database {
// 首先尝试专用只读连接 // 首先尝试专用只读连接
match self.read_connection.try_lock() { match self.read_connection.try_lock() {
Ok(conn) => { Ok(conn) => {
println!("使用专用只读连接");
return Ok(ConnectionHandle::Single(conn)); return Ok(ConnectionHandle::Single(conn));
}, },
Err(_) => { Err(_) => {

View File

@ -70,7 +70,6 @@ pub async fn get_all_models(
"模特仓库未初始化".to_string() "模特仓库未初始化".to_string()
})?; })?;
println!("调用 ModelService::get_all_models...");
let result = ModelService::get_all_models(repository) let result = ModelService::get_all_models(repository)
.map_err(|e| { .map_err(|e| {
println!("ModelService::get_all_models 失败: {}", e); println!("ModelService::get_all_models 失败: {}", e);

View File

@ -46,7 +46,6 @@ const ModelList: React.FC<ModelListProps> = ({ onModelSelect }) => {
try { try {
setLoading(true); setLoading(true);
setError(null); setError(null);
console.log('开始加载模特列表...');
const modelList = await modelService.getAllModels(); const modelList = await modelService.getAllModels();
console.log('模特列表加载成功:', modelList); console.log('模特列表加载成功:', modelList);
setModels(modelList); setModels(modelList);