- 新增每日数据保留清理任务:按保留期清空原始报文、删除过期 事件/审计记录,批量幂等可重跑;067 迁移补齐清理索引并清理 冗余索引,check-sql-guard 增加防线 - 生产 CORS 改为显式来源白名单(CORS_ALLOWED_ORIGINS),禁用 通配符,非通配响应补充 Vary: Origin - 文件上传任一步失败时回滚已上传对象,清理失败记录 warn 日志 - 新增 HTTP/CORS 与 PostgreSQL 集成测试;若干文件仅 prettier 重排
71 lines
2.3 KiB
TypeScript
71 lines
2.3 KiB
TypeScript
/**
|
|
* 数据库 SQL 约束检查。
|
|
* 该脚本检查关键防线是否被后续重构移除,不尝试替代 EXPLAIN 和生产监控。
|
|
*/
|
|
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url))
|
|
const BACKEND_ROOT = path.resolve(SCRIPT_DIR, '..')
|
|
|
|
const checks: Array<{ name: string; file: string; patterns: string[] }> = [
|
|
{
|
|
name: '慢查询阈值配置',
|
|
file: path.join(BACKEND_ROOT, 'src/config/env-overrides.ts'),
|
|
patterns: ['DATABASE_SLOW_QUERY_THRESHOLD_MS', 'slowQueryThresholdMs'],
|
|
},
|
|
{
|
|
name: '慢查询日志与参数脱敏',
|
|
file: path.join(BACKEND_ROOT, 'src/db/client.ts'),
|
|
patterns: ['logSlowQuery', 'fingerprintSql', '参数值始终不写入日志'],
|
|
},
|
|
{
|
|
name: '高频 pending 查询索引',
|
|
file: path.join(BACKEND_ROOT, 'src/db/migrations/058_database_query_guardrails.sql'),
|
|
patterns: [
|
|
'idx_worker_cancel_requests_pending_worker_order',
|
|
'idx_worker_feedbacks_pending_worker_order',
|
|
'idx_work_order_events_order_type_id',
|
|
],
|
|
},
|
|
{
|
|
name: '打手订单拼单查询限定当前页',
|
|
file: path.join(BACKEND_ROOT, 'src/repositories/worker-platform/work-order-share-repo.ts'),
|
|
patterns: ['workOrderIds: number[]', 'wos.work_order_id = ANY($2::bigint[])'],
|
|
},
|
|
{
|
|
name: '数据保留清理扫描索引',
|
|
file: path.join(BACKEND_ROOT, 'src/db/migrations/067_data_retention_indexes.sql'),
|
|
patterns: [
|
|
'idx_orders_updated_at',
|
|
'idx_kuaishou_industry_vouchers_updated_at',
|
|
'idx_task_events_created_at',
|
|
'idx_webhook_events_created_at',
|
|
'DROP INDEX IF EXISTS idx_work_product_match_logs_created_at',
|
|
],
|
|
},
|
|
]
|
|
|
|
const failures: string[] = []
|
|
for (const check of checks) {
|
|
if (!fs.existsSync(check.file)) {
|
|
failures.push(`${check.name}: 文件不存在 ${check.file}`)
|
|
continue
|
|
}
|
|
const source = fs.readFileSync(check.file, 'utf8')
|
|
for (const pattern of check.patterns) {
|
|
if (!source.includes(pattern)) {
|
|
failures.push(`${check.name}: 缺少约束 ${pattern} (${check.file})`)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (failures.length > 0) {
|
|
console.error('[sql-guard] SQL 约束检查失败')
|
|
for (const failure of failures) console.error(`[sql-guard] ${failure}`)
|
|
process.exitCode = 1
|
|
} else {
|
|
console.info('[sql-guard] SQL 约束检查通过')
|
|
}
|