feat(ops): 数据保留清理、CORS 白名单与上传失败回滚

- 新增每日数据保留清理任务:按保留期清空原始报文、删除过期
  事件/审计记录,批量幂等可重跑;067 迁移补齐清理索引并清理
  冗余索引,check-sql-guard 增加防线
- 生产 CORS 改为显式来源白名单(CORS_ALLOWED_ORIGINS),禁用
  通配符,非通配响应补充 Vary: Origin
- 文件上传任一步失败时回滚已上传对象,清理失败记录 warn 日志
- 新增 HTTP/CORS 与 PostgreSQL 集成测试;若干文件仅 prettier 重排
This commit is contained in:
yml2213
2026-08-30 13:59:01 +08:00
parent dcb0fa4c76
commit 02c9de8b9c
25 changed files with 489 additions and 71 deletions
@@ -0,0 +1,20 @@
-- 067_data_retention_indexes.sql —— 数据保留清理所需索引。
CREATE INDEX IF NOT EXISTS idx_webhook_events_created_at
ON webhook_events(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_orders_updated_at
ON orders(updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_kuaishou_industry_vouchers_updated_at
ON kuaishou_industry_vouchers(updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_task_events_created_at
ON task_events(created_at DESC);
DROP INDEX IF EXISTS idx_work_product_match_logs_created_at;
COMMENT ON INDEX idx_webhook_events_created_at IS '支持 webhook 回调去重数据按创建时间清理';
COMMENT ON INDEX idx_orders_updated_at IS '支持订单原始报文按更新时间清理';
COMMENT ON INDEX idx_kuaishou_industry_vouchers_updated_at IS '支持电子凭证原始报文按更新时间清理';
COMMENT ON INDEX idx_task_events_created_at IS '支持任务事件按创建时间清理';
@@ -0,0 +1,60 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { Pool } from 'pg'
const connectionString = process.env.TEST_POSTGRES_URL
test('real PostgreSQL transaction rolls back atomically', { skip: !connectionString }, async () => {
const pool = new Pool({ connectionString })
const client = await pool.connect()
const tableName = `integration_tx_${Date.now()}`
try {
await client.query(`CREATE TEMP TABLE ${tableName} (id integer primary key, value text)`)
await client.query('BEGIN')
await client.query(`INSERT INTO ${tableName} (id, value) VALUES (1, 'before-error')`)
try {
await client.query(`INSERT INTO ${tableName} (id, value) VALUES (1, 'duplicate')`)
} catch {
await client.query('ROLLBACK')
}
const result = await client.query(`SELECT COUNT(*)::int AS count FROM ${tableName}`)
assert.equal(result.rows[0].count, 0)
} finally {
client.release()
await pool.end()
}
})
test(
'real PostgreSQL enforces callback replay idempotency under concurrency',
{
skip: !connectionString,
},
async () => {
const pool = new Pool({ connectionString })
const tableName = `integration_webhook_${Date.now()}`
try {
await pool.query(
`CREATE TEMP TABLE ${tableName} (
id serial primary key,
provider text not null,
event_id text not null,
unique(provider, event_id)
)`,
)
await Promise.all(
Array.from({ length: 8 }, () =>
pool.query(
`INSERT INTO ${tableName} (provider, event_id)
VALUES ('integration', 'replay-1')
ON CONFLICT (provider, event_id) DO NOTHING`,
),
),
)
const result = await pool.query(`SELECT COUNT(*)::int AS count FROM ${tableName}`)
assert.equal(result.rows[0].count, 1)
} finally {
await pool.end()
}
},
)