tt-help-cli-ycl 1.4.23 → 1.4.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/migrate-raw-jobs.mjs +243 -0
- package/scripts/sync-raw-jobs.mjs +281 -0
- package/src/cli/raw-sync.js +41 -0
- package/src/lib/args.js +50 -0
- package/src/lib/constants.js +14 -0
- package/src/main.js +3 -0
- package/src/watch/data-store.js +178 -258
- package/src/watch/db-schema.js +1 -0
- package/src/watch/db-stats.js +18 -10
- package/src/watch/public/app.js +1 -1
- package/src/watch/server.js +117 -19
package/src/watch/data-store.js
CHANGED
|
@@ -1563,291 +1563,211 @@ export function createStore(filePath, options = {}) {
|
|
|
1563
1563
|
.filter(Boolean)
|
|
1564
1564
|
: [];
|
|
1565
1565
|
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
if (tier2List.length > 0)
|
|
1583
|
-
groups.push({ type: "include", values: tier2List });
|
|
1584
|
-
if (selected) {
|
|
1585
|
-
if (otherList.length > 0)
|
|
1586
|
-
groups.push({ type: "include", values: otherList });
|
|
1587
|
-
} else {
|
|
1588
|
-
groups.push({ type: "exclude", values: [...tier1, ...tier2] });
|
|
1589
|
-
}
|
|
1590
|
-
return groups;
|
|
1566
|
+
// ---- 过期 processing 回收 ----
|
|
1567
|
+
const expiredRow = getDb()
|
|
1568
|
+
.prepare(
|
|
1569
|
+
`SELECT * FROM jobs WHERE status = 'processing'
|
|
1570
|
+
AND claimed_at IS NOT NULL AND ? - claimed_at > ?
|
|
1571
|
+
ORDER BY claimed_at ASC LIMIT 1`,
|
|
1572
|
+
)
|
|
1573
|
+
.get(now, expireMs);
|
|
1574
|
+
let expiredCandidate = null;
|
|
1575
|
+
if (expiredRow) {
|
|
1576
|
+
getDb()
|
|
1577
|
+
.prepare(
|
|
1578
|
+
"UPDATE jobs SET status = 'pending', claimed_at = NULL WHERE unique_id = ?",
|
|
1579
|
+
)
|
|
1580
|
+
.run(expiredRow.unique_id);
|
|
1581
|
+
expiredCandidate = expiredRow;
|
|
1591
1582
|
}
|
|
1592
1583
|
|
|
1593
|
-
|
|
1584
|
+
// ---- 构建统一优先级查询(合并原 20+ 次独立 SELECT 为 1 次 UNION) ----
|
|
1585
|
+
const statusCodeWhere = loggedIn
|
|
1586
|
+
? "(status_code IS NULL OR status_code = 0 OR status_code = 209002)"
|
|
1587
|
+
: "(status_code IS NULL OR status_code = 0)";
|
|
1588
|
+
const notSellerWhere = loggedIn ? "" : "AND COALESCE(tt_seller, 0) != 1";
|
|
1589
|
+
|
|
1590
|
+
const tier1List = normalizedLocations.length
|
|
1591
|
+
? normalizedLocations.filter((loc) => tier1.has(loc))
|
|
1592
|
+
: [...tier1];
|
|
1593
|
+
const tier2List = normalizedLocations.length
|
|
1594
|
+
? normalizedLocations.filter((loc) => tier2.has(loc))
|
|
1595
|
+
: [...tier2];
|
|
1596
|
+
const otherFiltered = normalizedLocations.length
|
|
1597
|
+
? normalizedLocations.filter(
|
|
1598
|
+
(loc) => !tier1.has(loc) && !tier2.has(loc),
|
|
1599
|
+
)
|
|
1600
|
+
: null;
|
|
1594
1601
|
|
|
1595
|
-
|
|
1596
|
-
if (!
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1602
|
+
const locIn = (list) => {
|
|
1603
|
+
if (!list || list.length === 0) return null;
|
|
1604
|
+
const ph = list.map(() => "?").join(", ");
|
|
1605
|
+
return {
|
|
1606
|
+
sql: `(UPPER(COALESCE(guessed_location, '')) IN (${ph}) OR COALESCE(guessed_location, '') = '')`,
|
|
1607
|
+
params: [...list],
|
|
1608
|
+
};
|
|
1609
|
+
};
|
|
1610
|
+
const locNotIn = (list) => {
|
|
1611
|
+
if (!list || list.length === 0) return null;
|
|
1612
|
+
const ph = list.map(() => "?").join(", ");
|
|
1613
|
+
return {
|
|
1614
|
+
sql: `UPPER(COALESCE(guessed_location, '')) NOT IN (${ph})`,
|
|
1615
|
+
params: [...list],
|
|
1616
|
+
};
|
|
1617
|
+
};
|
|
1618
|
+
const locOther = () => {
|
|
1619
|
+
if (otherFiltered && otherFiltered.length > 0)
|
|
1620
|
+
return locIn(otherFiltered);
|
|
1621
|
+
if (normalizedLocations.length === 0)
|
|
1622
|
+
return locNotIn([...tier1, ...tier2]);
|
|
1623
|
+
return null;
|
|
1624
|
+
};
|
|
1609
1625
|
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
} else {
|
|
1618
|
-
// 登录:可以领取 status_code 为空、0、或 209002 的任务
|
|
1619
|
-
where.push(
|
|
1620
|
-
"(status_code IS NULL OR status_code = 0 OR status_code = 209002)",
|
|
1621
|
-
);
|
|
1622
|
-
}
|
|
1623
|
-
// 其他 status_code 值的任务不被领取
|
|
1624
|
-
if (requireVideo) {
|
|
1625
|
-
where.push("COALESCE(video_count, 0) > 0");
|
|
1626
|
-
}
|
|
1627
|
-
applyLocationGroup(where, args, group);
|
|
1628
|
-
for (const filter of filters) {
|
|
1629
|
-
where.push(filter);
|
|
1630
|
-
}
|
|
1631
|
-
return getDb()
|
|
1632
|
-
.prepare(
|
|
1633
|
-
`
|
|
1634
|
-
SELECT *
|
|
1635
|
-
FROM jobs
|
|
1636
|
-
WHERE ${where.join(" AND ")}
|
|
1637
|
-
ORDER BY follower_count DESC, created_at ASC, unique_id ASC
|
|
1638
|
-
LIMIT 1
|
|
1639
|
-
`,
|
|
1640
|
-
)
|
|
1641
|
-
.get(...args);
|
|
1626
|
+
// 全局 location 过滤(pinned 查询用)
|
|
1627
|
+
let pinnedLocSql = "";
|
|
1628
|
+
const pinnedLocParams = [];
|
|
1629
|
+
if (normalizedLocations.length > 0) {
|
|
1630
|
+
const ph = normalizedLocations.map(() => "?").join(", ");
|
|
1631
|
+
pinnedLocSql = `AND (UPPER(COALESCE(guessed_location, '')) IN (${ph}) OR COALESCE(guessed_location, '') = '')`;
|
|
1632
|
+
pinnedLocParams.push(...normalizedLocations);
|
|
1642
1633
|
}
|
|
1643
1634
|
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
for (const location of group.values) {
|
|
1647
|
-
const row = queryPendingOne({
|
|
1648
|
-
requireVideo,
|
|
1649
|
-
group: { type: "include", values: [location] },
|
|
1650
|
-
filters,
|
|
1651
|
-
});
|
|
1652
|
-
if (row) return row;
|
|
1653
|
-
}
|
|
1654
|
-
return null;
|
|
1655
|
-
}
|
|
1656
|
-
return queryPendingOne({ requireVideo, group, filters });
|
|
1657
|
-
}
|
|
1635
|
+
const sqlParts = [];
|
|
1636
|
+
const allParams = [];
|
|
1658
1637
|
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
if (requireVideo) {
|
|
1666
|
-
where.push("COALESCE(video_count, 0) > 0");
|
|
1667
|
-
}
|
|
1668
|
-
if (normalizedLocations.length > 0) {
|
|
1669
|
-
where.push(
|
|
1670
|
-
`(UPPER(COALESCE(guessed_location, '')) IN (${normalizedLocations.map(() => "?").join(", ")}) OR COALESCE(guessed_location, '') = '')`,
|
|
1671
|
-
);
|
|
1672
|
-
args.push(...normalizedLocations);
|
|
1673
|
-
}
|
|
1674
|
-
return getDb()
|
|
1675
|
-
.prepare(
|
|
1676
|
-
`
|
|
1677
|
-
SELECT *
|
|
1678
|
-
FROM jobs
|
|
1679
|
-
WHERE ${where.join(" AND ")}
|
|
1680
|
-
ORDER BY created_at ASC, unique_id ASC
|
|
1681
|
-
LIMIT 1
|
|
1682
|
-
`,
|
|
1683
|
-
)
|
|
1684
|
-
.get(...args);
|
|
1685
|
-
}
|
|
1638
|
+
const addPart = (prio, subPrio, conditions, params) => {
|
|
1639
|
+
sqlParts.push(
|
|
1640
|
+
`SELECT *, ${prio} as _prio, ${subPrio} as _sub_prio FROM jobs WHERE ${conditions}`,
|
|
1641
|
+
);
|
|
1642
|
+
allParams.push(...params);
|
|
1643
|
+
};
|
|
1686
1644
|
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
}
|
|
1645
|
+
// prio 0: pinned + hasVideo
|
|
1646
|
+
addPart(
|
|
1647
|
+
0,
|
|
1648
|
+
0,
|
|
1649
|
+
`status = 'pending' AND pinned = 1 AND video_count > 0 ${pinnedLocSql} AND ${statusCodeWhere} ${notSellerWhere}`,
|
|
1650
|
+
pinnedLocParams,
|
|
1651
|
+
);
|
|
1652
|
+
// prio 1: pinned (no video req)
|
|
1653
|
+
addPart(
|
|
1654
|
+
1,
|
|
1655
|
+
0,
|
|
1656
|
+
`status = 'pending' AND pinned = 1 ${pinnedLocSql} AND ${statusCodeWhere} ${notSellerWhere}`,
|
|
1657
|
+
pinnedLocParams,
|
|
1658
|
+
);
|
|
1699
1659
|
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1660
|
+
// prio 2-3: expired candidate(回收的超时任务优先)
|
|
1661
|
+
if (expiredCandidate) {
|
|
1662
|
+
addPart(
|
|
1663
|
+
2,
|
|
1664
|
+
0,
|
|
1665
|
+
`unique_id = ? AND video_count > 0 AND ${statusCodeWhere} ${notSellerWhere}`,
|
|
1666
|
+
[expiredCandidate.unique_id],
|
|
1667
|
+
);
|
|
1668
|
+
addPart(
|
|
1669
|
+
3,
|
|
1670
|
+
0,
|
|
1671
|
+
`unique_id = ? AND ${statusCodeWhere} ${notSellerWhere}`,
|
|
1672
|
+
[expiredCandidate.unique_id],
|
|
1673
|
+
);
|
|
1674
|
+
}
|
|
1714
1675
|
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
if (follow) return follow;
|
|
1729
|
-
}
|
|
1676
|
+
// 非 pinned 优先级:seed → seller → follow → other,每级 × 3 个 location tier × (hasVideo / noVideoReq)
|
|
1677
|
+
const priorities = [
|
|
1678
|
+
{
|
|
1679
|
+
filter: `instr(COALESCE(sources, ''), '"seed"') > 0`,
|
|
1680
|
+
loggedInOnly: false,
|
|
1681
|
+
},
|
|
1682
|
+
{ filter: `tt_seller = 1 AND verified = 0`, loggedInOnly: true },
|
|
1683
|
+
{
|
|
1684
|
+
filter: `(instr(COALESCE(sources, ''), '"following"') > 0 OR instr(COALESCE(sources, ''), '"follower"') > 0 OR instr(COALESCE(sources, ''), '"comment"') > 0)`,
|
|
1685
|
+
loggedInOnly: false,
|
|
1686
|
+
},
|
|
1687
|
+
{ filter: `1 = 1`, loggedInOnly: false },
|
|
1688
|
+
];
|
|
1730
1689
|
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1690
|
+
let basePrio = 4;
|
|
1691
|
+
for (const p of priorities) {
|
|
1692
|
+
if (p.loggedInOnly && !loggedIn) continue;
|
|
1693
|
+
const tierLocations = [
|
|
1694
|
+
{ list: tier1List, fn: locIn },
|
|
1695
|
+
{ list: tier2List, fn: locIn },
|
|
1696
|
+
{ list: null, fn: locOther },
|
|
1697
|
+
];
|
|
1698
|
+
let subPrio = 0;
|
|
1699
|
+
for (const tl of tierLocations) {
|
|
1700
|
+
const loc = tl.fn(tl.list);
|
|
1701
|
+
if (!loc) continue;
|
|
1702
|
+
// hasVideo
|
|
1703
|
+
addPart(
|
|
1704
|
+
basePrio,
|
|
1705
|
+
subPrio,
|
|
1706
|
+
`status = 'pending' AND pinned = 0 AND ${p.filter} AND video_count > 0 AND ${loc.sql} AND ${statusCodeWhere} ${notSellerWhere}`,
|
|
1707
|
+
loc.params,
|
|
1708
|
+
);
|
|
1709
|
+
subPrio++;
|
|
1710
|
+
// noVideoReq
|
|
1711
|
+
addPart(
|
|
1712
|
+
basePrio + 1,
|
|
1713
|
+
subPrio,
|
|
1714
|
+
`status = 'pending' AND pinned = 0 AND ${p.filter} AND ${loc.sql} AND ${statusCodeWhere} ${notSellerWhere}`,
|
|
1715
|
+
loc.params,
|
|
1716
|
+
);
|
|
1717
|
+
subPrio++;
|
|
1738
1718
|
}
|
|
1719
|
+
basePrio += 2;
|
|
1720
|
+
}
|
|
1739
1721
|
|
|
1740
|
-
|
|
1722
|
+
let selectedRow = null;
|
|
1723
|
+
if (sqlParts.length > 0) {
|
|
1724
|
+
const unionSql =
|
|
1725
|
+
sqlParts.join(" UNION ALL ") +
|
|
1726
|
+
" ORDER BY _prio, _sub_prio, follower_count DESC, created_at ASC, unique_id ASC LIMIT 1";
|
|
1727
|
+
selectedRow = getDb()
|
|
1728
|
+
.prepare(unionSql)
|
|
1729
|
+
.get(...allParams);
|
|
1741
1730
|
}
|
|
1742
1731
|
|
|
1743
|
-
|
|
1744
|
-
if (!row) return null;
|
|
1732
|
+
if (selectedRow) {
|
|
1745
1733
|
getDb()
|
|
1746
1734
|
.prepare(
|
|
1747
1735
|
"UPDATE jobs SET status = 'processing', claimed_at = ?, claimed_by = ? WHERE unique_id = ?",
|
|
1748
1736
|
)
|
|
1749
|
-
.run(now, userId,
|
|
1737
|
+
.run(now, userId, selectedRow.unique_id);
|
|
1750
1738
|
markStatsDirty();
|
|
1751
1739
|
return {
|
|
1752
|
-
uniqueId:
|
|
1753
|
-
nickname:
|
|
1740
|
+
uniqueId: selectedRow.unique_id,
|
|
1741
|
+
nickname: selectedRow.nickname,
|
|
1754
1742
|
claimedAt: now,
|
|
1755
1743
|
claimedBy: userId,
|
|
1756
1744
|
};
|
|
1757
1745
|
}
|
|
1758
1746
|
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
)
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
status: "pending",
|
|
1782
|
-
claimed_at: null,
|
|
1783
|
-
});
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
|
-
for (const requireVideo of [true, false]) {
|
|
1787
|
-
const pinned = findPinnedPending(requireVideo);
|
|
1788
|
-
if (pinned) {
|
|
1789
|
-
return claimRow(pinned);
|
|
1790
|
-
}
|
|
1791
|
-
if (expiredCandidate) {
|
|
1792
|
-
return claimRow({
|
|
1793
|
-
unique_id: expiredCandidate.uniqueId,
|
|
1794
|
-
nickname: expiredCandidate.nickname,
|
|
1795
|
-
});
|
|
1796
|
-
}
|
|
1797
|
-
const ranked = findPrioritizedPending(requireVideo);
|
|
1798
|
-
if (ranked) {
|
|
1799
|
-
return claimRow(ranked);
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
// 尝试从 raw_jobs 毛料库补充任务(使用 createStore 时配置的 LLM 打分)
|
|
1803
|
-
// 使用锁防止多个请求同时触发 LLM refill
|
|
1804
|
-
if (refillLock) {
|
|
1805
|
-
// 已有 refill 在进行中,等待完成后重新尝试领取
|
|
1806
|
-
await refillLock;
|
|
1807
|
-
for (const requireVideo of [true, false]) {
|
|
1808
|
-
const pinned = findPinnedPending(requireVideo);
|
|
1809
|
-
if (pinned) {
|
|
1810
|
-
return claimRow(pinned);
|
|
1811
|
-
}
|
|
1812
|
-
const ranked = findPrioritizedPending(requireVideo);
|
|
1813
|
-
if (ranked) {
|
|
1814
|
-
return claimRow(ranked);
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
return null;
|
|
1818
|
-
}
|
|
1819
|
-
const refillPromise = (async () => {
|
|
1820
|
-
const result = refillJobsFromRaw(
|
|
1821
|
-
normalizedLocations.length ? normalizedLocations : null,
|
|
1822
|
-
500,
|
|
1823
|
-
refillLlmConfig,
|
|
1824
|
-
);
|
|
1825
|
-
// refillJobsFromRaw 在 LLM 模式下返回 Promise
|
|
1826
|
-
if (result && typeof result.then === "function") {
|
|
1827
|
-
return await result;
|
|
1828
|
-
}
|
|
1829
|
-
return result;
|
|
1830
|
-
})();
|
|
1831
|
-
// 让并发请求等待同一个 refill,并且无论成功/失败都释放锁
|
|
1832
|
-
refillLock = refillPromise.finally(() => {
|
|
1833
|
-
refillLock = null;
|
|
1834
|
-
});
|
|
1835
|
-
|
|
1836
|
-
const refillResult = await refillLock;
|
|
1837
|
-
if (refillResult.moved > 0) {
|
|
1838
|
-
console.error(
|
|
1839
|
-
`[data-store] 从 raw_jobs 补充了 ${refillResult.moved} 条任务到 jobs`,
|
|
1840
|
-
);
|
|
1841
|
-
for (const requireVideo of [true, false]) {
|
|
1842
|
-
const pinned = findPinnedPending(requireVideo);
|
|
1843
|
-
if (pinned) {
|
|
1844
|
-
return claimRow(pinned);
|
|
1845
|
-
}
|
|
1846
|
-
const ranked = findPrioritizedPending(requireVideo);
|
|
1847
|
-
if (ranked) {
|
|
1848
|
-
return claimRow(ranked);
|
|
1747
|
+
// refill 异步化:不阻塞当前请求
|
|
1748
|
+
if (!refillLock) {
|
|
1749
|
+
refillLock = (async () => {
|
|
1750
|
+
try {
|
|
1751
|
+
const result = refillJobsFromRaw(
|
|
1752
|
+
normalizedLocations.length ? normalizedLocations : null,
|
|
1753
|
+
500,
|
|
1754
|
+
refillLlmConfig,
|
|
1755
|
+
);
|
|
1756
|
+
const r =
|
|
1757
|
+
result && typeof result.then === "function"
|
|
1758
|
+
? await result
|
|
1759
|
+
: result;
|
|
1760
|
+
if (r.moved > 0) {
|
|
1761
|
+
console.error(
|
|
1762
|
+
`[data-store] 从 raw_jobs 补充了 ${r.moved} 条任务到 jobs`,
|
|
1763
|
+
);
|
|
1764
|
+
}
|
|
1765
|
+
} catch (e) {
|
|
1766
|
+
console.error(`[data-store] refill 异常: ${e.message}`);
|
|
1767
|
+
} finally {
|
|
1768
|
+
refillLock = null;
|
|
1849
1769
|
}
|
|
1850
|
-
}
|
|
1770
|
+
})();
|
|
1851
1771
|
}
|
|
1852
1772
|
|
|
1853
1773
|
return null;
|
package/src/watch/db-schema.js
CHANGED
|
@@ -425,6 +425,7 @@ export function initDb(filePath) {
|
|
|
425
425
|
`CREATE INDEX IF NOT EXISTS idx_cross_country_source ON cross_country_learnings(source_country, result_status)`,
|
|
426
426
|
`CREATE INDEX IF NOT EXISTS idx_cross_country_target ON cross_country_learnings(target_country)`,
|
|
427
427
|
`CREATE INDEX IF NOT EXISTS idx_tags_dead_reason ON tags(dead_reason)`,
|
|
428
|
+
`CREATE INDEX IF NOT EXISTS idx_jobs_recheck ON jobs(status, updated_at) WHERE status = 'done' AND (tt_seller IS NULL OR tt_seller != 1)`,
|
|
428
429
|
];
|
|
429
430
|
for (const sql of indexes) {
|
|
430
431
|
db.exec(sql);
|
package/src/watch/db-stats.js
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
import { getDb } from "./db-schema.js";
|
|
8
8
|
import { getRawJobsCount, getUserDbCount } from "./db-crud.js";
|
|
9
9
|
|
|
10
|
-
export function getDashboardStatsFromDb(targetLocations = []) {
|
|
10
|
+
export function getDashboardStatsFromDb(targetLocations = [], options = {}) {
|
|
11
11
|
const db = getDb();
|
|
12
12
|
if (!db) return null;
|
|
13
13
|
|
|
14
|
+
const skipSlow = !!options.skipSlowCounts;
|
|
14
15
|
const targetPlaceholders = targetLocations.map(() => "?").join(", ");
|
|
15
16
|
const targetParams = targetLocations.length ? targetLocations : [];
|
|
16
17
|
|
|
@@ -89,21 +90,28 @@ export function getDashboardStatsFromDb(targetLocations = []) {
|
|
|
89
90
|
.prepare("SELECT COUNT(*) as total FROM jobs_base")
|
|
90
91
|
.get().total;
|
|
91
92
|
|
|
92
|
-
const tagCount =
|
|
93
|
+
const tagCount = skipSlow
|
|
94
|
+
? 0
|
|
95
|
+
: db.prepare("SELECT COUNT(*) as total FROM tags").get().total;
|
|
93
96
|
|
|
94
|
-
const commentTaskCount =
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
const commentTaskCount = skipSlow
|
|
98
|
+
? 0
|
|
99
|
+
: db
|
|
100
|
+
.prepare(
|
|
101
|
+
`SELECT COUNT(*) as total FROM videos
|
|
102
|
+
WHERE user_update_count IS NULL OR user_update_count <= 0`,
|
|
103
|
+
)
|
|
104
|
+
.get().total;
|
|
105
|
+
|
|
106
|
+
const rawJobsCount = skipSlow ? 0 : getRawJobsCount();
|
|
107
|
+
const dbUsersCount = skipSlow ? 0 : getUserDbCount();
|
|
100
108
|
|
|
101
109
|
return {
|
|
102
110
|
totalUsers: aggregateRow.total,
|
|
103
111
|
tagCount,
|
|
104
112
|
commentTaskCount,
|
|
105
|
-
rawJobs:
|
|
106
|
-
dbTotalUsers:
|
|
113
|
+
rawJobs: rawJobsCount,
|
|
114
|
+
dbTotalUsers: dbUsersCount,
|
|
107
115
|
jobsTotal: aggregateRow.total,
|
|
108
116
|
jobsBaseTotal: jobsBaseCount,
|
|
109
117
|
jobsPending: aggregateRow.pending,
|
package/src/watch/public/app.js
CHANGED
|
@@ -3356,7 +3356,7 @@ fetchClientErrors();
|
|
|
3356
3356
|
initTableSorting();
|
|
3357
3357
|
initTargetTableSorting();
|
|
3358
3358
|
|
|
3359
|
-
//
|
|
3359
|
+
// 错峰轮询(服务端已缓存,瞬时响应无压力)
|
|
3360
3360
|
setInterval(fetchStats, 10000);
|
|
3361
3361
|
setTimeout(() => setInterval(fetchUsers, 10000), 2500);
|
|
3362
3362
|
setTimeout(() => setInterval(fetchClientErrors, 10000), 5000);
|