Commit 46f11430 46f1143019633de281de9bdfe272226d7b8c8f42 by 沈秋雨

refactor(database): 优化数据库连接管理与界面渲染流程

1 parent fcf7434e
...@@ -1085,9 +1085,18 @@ ...@@ -1085,9 +1085,18 @@
1085 showSyncNotice(''); 1085 showSyncNotice('');
1086 renderList(); 1086 renderList();
1087 await loadQueryRows(); 1087 await loadQueryRows();
1088 await claimCurrentReview(); 1088 // 先渲染详情(歌词加载立即开始),领取审核在后台并行
1089 renderList();
1090 renderDetail(); 1089 renderDetail();
1090 claimCurrentReview().then(() => {
1091 // 领取完成后只更新顶部状态信息(不重新渲染整个详情)
1092 const q = currentQuery();
1093 if (q && q.query_source_id === state.queryId) {
1094 els.stickyInfo.querySelectorAll('.pill').forEach(el => {
1095 // 标记“我正在审核”已在 renderDetail 中生成,此处只做局部刷新
1096 });
1097 renderList();
1098 }
1099 });
1091 }); 1100 });
1092 } 1101 }
1093 els.prevPageBtn.disabled = state.page <= 1; 1102 els.prevPageBtn.disabled = state.page <= 1;
...@@ -1382,9 +1391,9 @@ ...@@ -1382,9 +1391,9 @@
1382 } 1391 }
1383 const payload = await updateStagingReview(query, dbDecision, note); 1392 const payload = await updateStagingReview(query, dbDecision, note);
1384 for (const row of state.queryRows) applyReviewSnapshot(row, payload.record); 1393 for (const row of state.queryRows) applyReviewSnapshot(row, payload.record);
1385 await reloadSummary(); 1394 // 刷新统计和加载列表并行
1386 // 重新加载列表并跳转到下一条 1395 await Promise.all([reloadSummary(), loadGroups()]);
1387 await advanceToNext(); 1396 renderAll();
1388 } catch (e) { 1397 } catch (e) {
1389 console.error('DB同步失败:', e); 1398 console.error('DB同步失败:', e);
1390 if (e.status === 409) { 1399 if (e.status === 409) {
...@@ -1408,8 +1417,9 @@ ...@@ -1408,8 +1417,9 @@
1408 } 1417 }
1409 await updateStagingReview(query, 'deleted', 1418 await updateStagingReview(query, 'deleted',
1410 document.getElementById('reviewNote')?.value || ''); 1419 document.getElementById('reviewNote')?.value || '');
1411 await reloadSummary(); 1420 // 刷新统计和加载列表并行
1412 await advanceToNext(); 1421 await Promise.all([reloadSummary(), loadGroups()]);
1422 renderAll();
1413 } catch (e) { 1423 } catch (e) {
1414 alert(`删除失败: ${e.message}`); 1424 alert(`删除失败: ${e.message}`);
1415 renderDetail(); 1425 renderDetail();
...@@ -1428,8 +1438,8 @@ ...@@ -1428,8 +1438,8 @@
1428 } 1438 }
1429 await updateStagingReview(query, 'pending', ''); 1439 await updateStagingReview(query, 'pending', '');
1430 if (candidate) setReview(candidate, { final_decision: undefined, note: '' }); 1440 if (candidate) setReview(candidate, { final_decision: undefined, note: '' });
1431 await loadQueryRows(); 1441 // 刷新统计和重新加载当前行并行
1432 await reloadSummary(); 1442 await Promise.all([reloadSummary(), loadQueryRows()]);
1433 } catch (e) { 1443 } catch (e) {
1434 alert(`撤销失败: ${e.message}`); 1444 alert(`撤销失败: ${e.message}`);
1435 } 1445 }
...@@ -1504,8 +1514,8 @@ ...@@ -1504,8 +1514,8 @@
1504 try { 1514 try {
1505 const payload = await reviewPostJSON('/api/import-reviewed'); 1515 const payload = await reviewPostJSON('/api/import-reviewed');
1506 alert(`入库完成:${payload.inserted_count} 条。结果文件:${payload.review_csv}`); 1516 alert(`入库完成:${payload.inserted_count} 条。结果文件:${payload.review_csv}`);
1507 await loadGroups(); 1517 // 刷新统计和加载列表并行
1508 await reloadSummary(); 1518 await Promise.all([reloadSummary(), loadGroups()]);
1509 renderAll(); 1519 renderAll();
1510 } catch (err) { 1520 } catch (err) {
1511 alert(`入库失败:${err.message}`); 1521 alert(`入库失败:${err.message}`);
...@@ -1575,6 +1585,7 @@ ...@@ -1575,6 +1585,7 @@
1575 1585
1576 async function advanceToNext() { 1586 async function advanceToNext() {
1577 // 重新加载列表(应用当前筛选,已审核的会被过滤掉) 1587 // 重新加载列表(应用当前筛选,已审核的会被过滤掉)
1588 // loadGroups 内部已包含 loadQueryRows,无需额外调用
1578 await loadGroups(); 1589 await loadGroups();
1579 renderAll(); 1590 renderAll();
1580 } 1591 }
......
...@@ -7,6 +7,7 @@ charset-normalizer==3.4.7 ...@@ -7,6 +7,7 @@ charset-normalizer==3.4.7
7 cos_python_sdk_v5==1.9.44 7 cos_python_sdk_v5==1.9.44
8 crcmod==1.7 8 crcmod==1.7
9 cryptography==49.0.0 9 cryptography==49.0.0
10 DBUtils>=3.1.0
10 et_xmlfile==2.0.0 11 et_xmlfile==2.0.0
11 exceptiongroup==1.3.1 12 exceptiongroup==1.3.1
12 idna==3.18 13 idna==3.18
......
...@@ -19,6 +19,7 @@ from urllib.parse import parse_qs, unquote, urlparse ...@@ -19,6 +19,7 @@ from urllib.parse import parse_qs, unquote, urlparse
19 19
20 import pymysql 20 import pymysql
21 import requests 21 import requests
22 from dbutils.pooled_db import PooledDB
22 from dotenv import load_dotenv 23 from dotenv import load_dotenv
23 24
24 25
...@@ -45,6 +46,18 @@ TARGET_DB_CONFIG = { ...@@ -45,6 +46,18 @@ TARGET_DB_CONFIG = {
45 } 46 }
46 TARGET_TABLE_NAME = os.getenv("TARGET_TABLE_NAME", "hk_songs") 47 TARGET_TABLE_NAME = os.getenv("TARGET_TABLE_NAME", "hk_songs")
47 TARGET_TABLE_NAME_TMP = os.getenv("TARGET_TABLE_NAME_TMP", f"{TARGET_TABLE_NAME}_import_staging") 48 TARGET_TABLE_NAME_TMP = os.getenv("TARGET_TABLE_NAME_TMP", f"{TARGET_TABLE_NAME}_import_staging")
49
50 # 连接池:mincached=2 保持 2 个空闲连接,maxcached=5 上限 5 个空闲,
51 # maxconnections=10 最大总数,ping=1 每次取连接前检测活性
52 _DB_POOL = PooledDB(
53 creator=pymysql,
54 mincached=2,
55 maxcached=5,
56 maxconnections=10,
57 blocking=True,
58 ping=1,
59 **TARGET_DB_CONFIG,
60 )
48 STAGING_DB_PATH = "__staging_db__" 61 STAGING_DB_PATH = "__staging_db__"
49 TARGET_COLUMNS = [ 62 TARGET_COLUMNS = [
50 "id", "name", "lyricist", "composer", "issue_status", "intro", 63 "id", "name", "lyricist", "composer", "issue_status", "intro",
...@@ -175,7 +188,8 @@ def _iter_csv(path: Path): ...@@ -175,7 +188,8 @@ def _iter_csv(path: Path):
175 188
176 189
177 def _target_conn(): 190 def _target_conn():
178 return pymysql.connect(**TARGET_DB_CONFIG) 191 """从连接池获取连接,with 块结束时自动归还池中。"""
192 return _DB_POOL.connection()
179 193
180 194
181 def _ensure_review_schema(apply_migration: bool = False) -> None: 195 def _ensure_review_schema(apply_migration: bool = False) -> None:
......