refactor: 全面优化后端代码质量与架构
- app.ts 单体路由拆分为 routes/ + helpers + middleware + static 独立模块 - 类型去重:CheckFailure/CheckResult 以 shared/api.ts 为唯一源头,收紧 phase 联合类型 - es-toolkit 替换:isPlainObject/isNil/isEmptyObject/isEqual/isError/Semaphore/groupBy - Bun 内置 API:Object.fromEntries 替代手写 headersToRecord - bun:sqlite 规范:prepare() → query() 利用内置缓存,避免 N+1 查询 - 新增 getLatestChecksMap/allGetTargetStats 批量查询方法 - 新增 backend-code-quality/api-route-separation/batch-data-queries 规范 - 补充 openspec/config.yaml 后端开发规范与 DEVELOPMENT.md 后端开发指引
This commit is contained in:
@@ -112,7 +112,7 @@ export class ProbeStore {
|
||||
}): void {
|
||||
if (this.closed) return;
|
||||
this.db
|
||||
.prepare(
|
||||
.query(
|
||||
"INSERT INTO check_results (target_id, timestamp, matched, duration_ms, status_detail, failure) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
)
|
||||
.run(
|
||||
@@ -139,12 +139,12 @@ export class ProbeStore {
|
||||
pageSize = 20,
|
||||
): { items: StoredCheckResult[]; total: number; page: number; pageSize: number } {
|
||||
const countRow = this.db
|
||||
.prepare("SELECT COUNT(*) as total FROM check_results WHERE target_id = ? AND timestamp >= ? AND timestamp <= ?")
|
||||
.query("SELECT COUNT(*) as total FROM check_results WHERE target_id = ? AND timestamp >= ? AND timestamp <= ?")
|
||||
.get(targetId, from, to) as { total: number };
|
||||
|
||||
const offset = (page - 1) * pageSize;
|
||||
const items = this.db
|
||||
.prepare(
|
||||
.query(
|
||||
"SELECT * FROM check_results WHERE target_id = ? AND timestamp >= ? AND timestamp <= ? ORDER BY timestamp DESC LIMIT ? OFFSET ?",
|
||||
)
|
||||
.all(targetId, from, to, pageSize, offset) as StoredCheckResult[];
|
||||
@@ -157,7 +157,7 @@ export class ProbeStore {
|
||||
availability: number;
|
||||
} {
|
||||
const row = this.db
|
||||
.prepare(
|
||||
.query(
|
||||
`SELECT
|
||||
COUNT(*) as totalChecks,
|
||||
COALESCE(SUM(CASE WHEN matched = 1 THEN 1 ELSE 0 END), 0) as upCount
|
||||
@@ -186,7 +186,7 @@ export class ProbeStore {
|
||||
totalChecks: number;
|
||||
}> {
|
||||
return this.db
|
||||
.prepare(
|
||||
.query(
|
||||
`SELECT
|
||||
strftime('%Y-%m-%dT%H:00:00', timestamp) as hour,
|
||||
AVG(CASE WHEN matched = 1 THEN duration_ms END) as avgDurationMs,
|
||||
@@ -212,12 +212,13 @@ export class ProbeStore {
|
||||
lastCheckTime: string | null;
|
||||
} {
|
||||
const targets = this.getTargets();
|
||||
const latestChecksMap = this.getLatestChecksMap();
|
||||
let up = 0;
|
||||
let down = 0;
|
||||
let lastCheckTime: string | null = null;
|
||||
|
||||
for (const target of targets) {
|
||||
const latest = this.getLatestCheck(target.id);
|
||||
const latest = latestChecksMap.get(target.id);
|
||||
|
||||
if (latest) {
|
||||
if (latest.matched) {
|
||||
@@ -247,7 +248,7 @@ export class ProbeStore {
|
||||
limit: number,
|
||||
): Array<{ timestamp: string; duration_ms: number | null; matched: number }> {
|
||||
return this.db
|
||||
.prepare(
|
||||
.query(
|
||||
"SELECT timestamp, duration_ms, matched FROM check_results WHERE target_id = ? ORDER BY timestamp DESC LIMIT ?",
|
||||
)
|
||||
.all(targetId, limit) as Array<{
|
||||
@@ -257,6 +258,38 @@ export class ProbeStore {
|
||||
}>;
|
||||
}
|
||||
|
||||
getLatestChecksMap(): Map<number, StoredCheckResult> {
|
||||
const rows = this.db
|
||||
.query(
|
||||
`SELECT cr.* FROM check_results cr
|
||||
INNER JOIN (
|
||||
SELECT target_id, MAX(timestamp) as max_ts
|
||||
FROM check_results
|
||||
GROUP BY target_id
|
||||
) latest ON cr.target_id = latest.target_id AND cr.timestamp = latest.max_ts`,
|
||||
)
|
||||
.all() as StoredCheckResult[];
|
||||
return new Map(rows.map((r) => [r.target_id, r]));
|
||||
}
|
||||
|
||||
getAllTargetStats(): Map<number, { totalChecks: number; availability: number }> {
|
||||
const rows = this.db
|
||||
.query(
|
||||
`SELECT target_id, COUNT(*) as totalChecks,
|
||||
COALESCE(SUM(CASE WHEN matched = 1 THEN 1 ELSE 0 END), 0) as upCount
|
||||
FROM check_results
|
||||
GROUP BY target_id`,
|
||||
)
|
||||
.all() as Array<{ target_id: number; totalChecks: number; upCount: number }>;
|
||||
|
||||
const result = new Map<number, { totalChecks: number; availability: number }>();
|
||||
for (const row of rows) {
|
||||
const availability = row.totalChecks > 0 ? Math.round((row.upCount / row.totalChecks) * 10000) / 100 : 0;
|
||||
result.set(row.target_id, { totalChecks: row.totalChecks, availability });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.closed = true;
|
||||
this.db.close();
|
||||
|
||||
Reference in New Issue
Block a user