refactor(database): 优化数据库连接管理与界面渲染流程
Showing
3 changed files
with
38 additions
and
12 deletions
| ... | @@ -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: | ... | ... |
-
Please register or sign in to post a comment