fix: 修复模板导入功能的关键问题
修复问题: - 修复数据库状态格式不一致导致的列表显示错误 - 修复单个导入完成后统计信息显示为0的问题 - 修复日期时间解析错误导致的panic问题 - 修复所有unwrap()调用导致的潜在崩溃 技术改进: - 统一使用Debug格式保存和查询import_status - 改进日期解析支持多种格式(RFC3339和SQLite格式) - 优化进度监控逻辑,保留最后有效统计数据 - 完善错误处理,避免锁中毒和解析错误 功能完善: - 模板导入完成后正确显示'已完成'状态 - 统计信息准确显示成功/失败数量 - 进度监控稳定运行,无无限轮询问题
This commit is contained in:
parent
939efd70d4
commit
2d88274c3a
|
|
@ -478,14 +478,7 @@ impl TemplateImportService {
|
||||||
let conn = self.database.get_connection();
|
let conn = self.database.get_connection();
|
||||||
let conn = conn.lock().map_err(|e| anyhow!("获取数据库连接失败: {}", e))?;
|
let conn = conn.lock().map_err(|e| anyhow!("获取数据库连接失败: {}", e))?;
|
||||||
|
|
||||||
let status_str = match status {
|
let status_str = format!("{:?}", status);
|
||||||
ImportStatus::Pending => "pending",
|
|
||||||
ImportStatus::Parsing => "parsing",
|
|
||||||
ImportStatus::Uploading => "uploading",
|
|
||||||
ImportStatus::Processing => "processing",
|
|
||||||
ImportStatus::Completed => "completed",
|
|
||||||
ImportStatus::Failed => "failed",
|
|
||||||
};
|
|
||||||
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"UPDATE templates SET import_status = ?, updated_at = datetime('now') WHERE id = ?",
|
"UPDATE templates SET import_status = ?, updated_at = datetime('now') WHERE id = ?",
|
||||||
|
|
|
||||||
|
|
@ -399,12 +399,34 @@ impl TemplateService {
|
||||||
tracks: Vec::new(), // 稍后填充
|
tracks: Vec::new(), // 稍后填充
|
||||||
import_status,
|
import_status,
|
||||||
source_file_path: row.get("source_file_path")?,
|
source_file_path: row.get("source_file_path")?,
|
||||||
created_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>("created_at")?)
|
created_at: {
|
||||||
.map_err(|_e| rusqlite::Error::InvalidColumnType(0, "created_at".to_string(), rusqlite::types::Type::Text))?
|
let created_at_str: String = row.get("created_at")?;
|
||||||
.with_timezone(&chrono::Utc),
|
chrono::DateTime::parse_from_rfc3339(&created_at_str)
|
||||||
updated_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>("updated_at")?)
|
.or_else(|_| {
|
||||||
.map_err(|_e| rusqlite::Error::InvalidColumnType(0, "updated_at".to_string(), rusqlite::types::Type::Text))?
|
// 尝试解析SQLite格式的日期
|
||||||
.with_timezone(&chrono::Utc),
|
chrono::NaiveDateTime::parse_from_str(&created_at_str, "%Y-%m-%d %H:%M:%S")
|
||||||
|
.map(|dt| dt.and_utc().fixed_offset())
|
||||||
|
})
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("❌ 解析created_at失败: '{}', 错误: {}", created_at_str, e);
|
||||||
|
rusqlite::Error::InvalidColumnType(0, "created_at".to_string(), rusqlite::types::Type::Text)
|
||||||
|
})?
|
||||||
|
.with_timezone(&chrono::Utc)
|
||||||
|
},
|
||||||
|
updated_at: {
|
||||||
|
let updated_at_str: String = row.get("updated_at")?;
|
||||||
|
chrono::DateTime::parse_from_rfc3339(&updated_at_str)
|
||||||
|
.or_else(|_| {
|
||||||
|
// 尝试解析SQLite格式的日期
|
||||||
|
chrono::NaiveDateTime::parse_from_str(&updated_at_str, "%Y-%m-%d %H:%M:%S")
|
||||||
|
.map(|dt| dt.and_utc().fixed_offset())
|
||||||
|
})
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("❌ 解析updated_at失败: '{}', 错误: {}", updated_at_str, e);
|
||||||
|
rusqlite::Error::InvalidColumnType(0, "updated_at".to_string(), rusqlite::types::Type::Text)
|
||||||
|
})?
|
||||||
|
.with_timezone(&chrono::Utc)
|
||||||
|
},
|
||||||
is_active: row.get("is_active")?,
|
is_active: row.get("is_active")?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -447,12 +469,32 @@ impl TemplateService {
|
||||||
height: row.get::<_, Option<i64>>("height")?.map(|h| h as u32),
|
height: row.get::<_, Option<i64>>("height")?.map(|h| h as u32),
|
||||||
upload_status,
|
upload_status,
|
||||||
metadata: row.get("metadata")?,
|
metadata: row.get("metadata")?,
|
||||||
created_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>("created_at")?)
|
created_at: {
|
||||||
.unwrap()
|
let created_at_str: String = row.get("created_at")?;
|
||||||
.with_timezone(&chrono::Utc),
|
chrono::DateTime::parse_from_rfc3339(&created_at_str)
|
||||||
updated_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>("updated_at")?)
|
.or_else(|_| {
|
||||||
.unwrap()
|
chrono::NaiveDateTime::parse_from_str(&created_at_str, "%Y-%m-%d %H:%M:%S")
|
||||||
.with_timezone(&chrono::Utc),
|
.map(|dt| dt.and_utc().fixed_offset())
|
||||||
|
})
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("❌ 解析素材created_at失败: '{}', 错误: {}", created_at_str, e);
|
||||||
|
rusqlite::Error::InvalidColumnType(0, "created_at".to_string(), rusqlite::types::Type::Text)
|
||||||
|
})?
|
||||||
|
.with_timezone(&chrono::Utc)
|
||||||
|
},
|
||||||
|
updated_at: {
|
||||||
|
let updated_at_str: String = row.get("updated_at")?;
|
||||||
|
chrono::DateTime::parse_from_rfc3339(&updated_at_str)
|
||||||
|
.or_else(|_| {
|
||||||
|
chrono::NaiveDateTime::parse_from_str(&updated_at_str, "%Y-%m-%d %H:%M:%S")
|
||||||
|
.map(|dt| dt.and_utc().fixed_offset())
|
||||||
|
})
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("❌ 解析素材updated_at失败: '{}', 错误: {}", updated_at_str, e);
|
||||||
|
rusqlite::Error::InvalidColumnType(0, "updated_at".to_string(), rusqlite::types::Type::Text)
|
||||||
|
})?
|
||||||
|
.with_timezone(&chrono::Utc)
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ export const ImportProgressModal: React.FC<ImportProgressModalProps> = ({
|
||||||
onComplete,
|
onComplete,
|
||||||
}) => {
|
}) => {
|
||||||
const [progress, setProgress] = useState<ImportProgress | null>(null);
|
const [progress, setProgress] = useState<ImportProgress | null>(null);
|
||||||
|
const [lastValidProgress, setLastValidProgress] = useState<ImportProgress | null>(null);
|
||||||
const { getImportProgress } = useTemplateStore();
|
const { getImportProgress } = useTemplateStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -28,23 +29,24 @@ export const ImportProgressModal: React.FC<ImportProgressModalProps> = ({
|
||||||
const progressData = await getImportProgress(templateId);
|
const progressData = await getImportProgress(templateId);
|
||||||
if (!isMounted) return;
|
if (!isMounted) return;
|
||||||
|
|
||||||
|
// 如果有有效的进度数据,保存它并更新显示
|
||||||
|
if (progressData) {
|
||||||
setProgress(progressData);
|
setProgress(progressData);
|
||||||
|
setLastValidProgress(progressData);
|
||||||
|
}
|
||||||
|
|
||||||
// 如果进度数据为空(已被清除),说明导入已完成,停止轮询
|
// 如果进度数据为空(已被清除),说明导入已完成,停止轮询
|
||||||
if (!progressData) {
|
if (!progressData) {
|
||||||
// 设置一个完成状态的进度对象
|
// 使用最后一次有效的进度数据,但更新状态为完成
|
||||||
|
if (lastValidProgress) {
|
||||||
const completedProgress: ImportProgress = {
|
const completedProgress: ImportProgress = {
|
||||||
template_id: templateId,
|
...lastValidProgress,
|
||||||
template_name: "模板",
|
|
||||||
status: ImportStatus.Completed,
|
status: ImportStatus.Completed,
|
||||||
total_materials: 0,
|
|
||||||
uploaded_materials: 0,
|
|
||||||
failed_materials: 0,
|
|
||||||
current_operation: "导入完成",
|
current_operation: "导入完成",
|
||||||
error_message: undefined,
|
|
||||||
progress_percentage: 100
|
progress_percentage: 100
|
||||||
};
|
};
|
||||||
setProgress(completedProgress);
|
setProgress(completedProgress);
|
||||||
|
}
|
||||||
|
|
||||||
// 清除轮询
|
// 清除轮询
|
||||||
if (interval) {
|
if (interval) {
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ const TemplateManagement: React.FC = () => {
|
||||||
page: currentPage,
|
page: currentPage,
|
||||||
page_size: pageSize,
|
page_size: pageSize,
|
||||||
});
|
});
|
||||||
|
console.log(response)
|
||||||
setTemplates(response.templates);
|
setTemplates(response.templates);
|
||||||
setTotalPages(Math.ceil(response.total / pageSize));
|
setTotalPages(Math.ceil(response.total / pageSize));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue