feat(ops): 数据保留清理、CORS 白名单与上传失败回滚
- 新增每日数据保留清理任务:按保留期清空原始报文、删除过期 事件/审计记录,批量幂等可重跑;067 迁移补齐清理索引并清理 冗余索引,check-sql-guard 增加防线 - 生产 CORS 改为显式来源白名单(CORS_ALLOWED_ORIGINS),禁用 通配符,非通配响应补充 Vary: Origin - 文件上传任一步失败时回滚已上传对象,清理失败记录 warn 日志 - 新增 HTTP/CORS 与 PostgreSQL 集成测试;若干文件仅 prettier 重排
This commit is contained in:
@@ -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()
|
||||
}
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user