feat(M01-C): 建立MySQL异步任务基础

This commit is contained in:
Codex
2026-06-18 10:20:39 +08:00
parent a2d578f73b
commit 2b90d1f101
15 changed files with 609 additions and 30 deletions
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url';
import { loadConfig } from '../dist/config.js';
import { closeMySqlPool, createMySqlPool } from '../dist/db/mysql.js';
import { LegacyReadRepository } from '../dist/db/legacy-read-repository.js';
import { TaskRepository } from '../dist/tasks/task-repository.js';
import {
executeMigrationPlan,
loadMigrationPlan,
@@ -12,11 +13,13 @@ import {
} from '../dist/db/migration-runner.js';
const expectedTables = [
'qipai_async_tasks',
'qipai_audit_logs',
'qipai_devices',
'qipai_legacy_table_mappings',
'qipai_members',
'qipai_orders',
'qipai_outbox_events',
'qipai_payments',
'qipai_rooms',
'qipai_schema_migrations',
@@ -38,12 +41,13 @@ async function readCoreTables(pool) {
return rows.map((row) => row.tableName);
}
async function readMigrationVersion(pool) {
async function readMigrationVersions(pool) {
const [rows] = await pool.query(
`SELECT version, name
FROM qipai_schema_migrations
WHERE version = ?`,
['2026061601']
WHERE version IN (?, ?)
ORDER BY version`,
['2026061601', '2026061802']
);
return rows;
}
@@ -79,11 +83,48 @@ async function assertLegacyCompatibility(pool) {
assert.equal(orders[0].refundAmountCents, null);
}
async function assertTaskDurability(pool) {
await pool.query(
`INSERT INTO qipai_tenants (code, name)
VALUES ('M01C-TEST', 'M01C sanitized test tenant')`
);
const firstRepository = new TaskRepository(pool);
const firstEnqueue = await firstRepository.enqueue({
tenantId: '1',
taskType: 'order.advance',
idempotencyKey: 'order:501:paid',
payload: { orderId: '501' },
maxAttempts: 3
});
const duplicateEnqueue = await firstRepository.enqueue({
tenantId: '1',
taskType: 'order.advance',
idempotencyKey: 'order:501:paid',
payload: { orderId: '501' },
maxAttempts: 3
});
assert.equal(firstEnqueue.created, true);
assert.equal(duplicateEnqueue.created, false);
assert.equal(duplicateEnqueue.id, firstEnqueue.id);
const restartedRepository = new TaskRepository(pool);
const claimed = await restartedRepository.claimNext('worker-after-restart', 30_000);
assert.equal(claimed?.id, firstEnqueue.id);
assert.equal(claimed?.attempts, 1);
assert.equal(await restartedRepository.complete(claimed.id, 'worker-after-restart'), true);
const [rows] = await pool.query(
`SELECT status, attempts FROM qipai_async_tasks WHERE id = ?`,
[firstEnqueue.id]
);
assert.deepEqual(rows, [{ status: 'SUCCEEDED', attempts: 1 }]);
}
const config = loadConfig();
assert.equal(config.mysql.passwordConfigured, true, 'Live migration test requires a temporary password.');
assert.match(
config.mysql.database,
/^qipai_m01b_test_[a-z0-9_]+$/,
/^qipai_m01c_test_[a-z0-9_]+$/,
'Live migration test refuses to use a non-temporary database.'
);
@@ -102,25 +143,26 @@ try {
await executeMigrationPlan(pool, plans.up);
await executeMigrationPlan(pool, plans.verify);
assert.deepEqual(await readCoreTables(pool), expectedTables);
assert.deepEqual(await readMigrationVersion(pool), [{
version: '2026061601',
name: 'm01b_core_schema'
}]);
assert.deepEqual(await readMigrationVersions(pool), [
{ version: '2026061601', name: 'm01b_core_schema' },
{ version: '2026061802', name: 'm01c_async_tasks' }
]);
await assertTaskDurability(pool);
await assertLegacyCompatibility(pool);
console.log('PASS: first up and verify completed.');
console.log('PASS: first up, verify and durable task restart check completed.');
await executeMigrationPlan(pool, plans.down);
assert.deepEqual(await readCoreTables(pool), []);
await assertLegacyCompatibility(pool);
console.log('PASS: down removed all M01-B core tables.');
console.log('PASS: down removed all M01-B/M01-C tables.');
await executeMigrationPlan(pool, plans.up);
await executeMigrationPlan(pool, plans.verify);
assert.deepEqual(await readCoreTables(pool), expectedTables);
assert.deepEqual(await readMigrationVersion(pool), [{
version: '2026061601',
name: 'm01b_core_schema'
}]);
assert.deepEqual(await readMigrationVersions(pool), [
{ version: '2026061601', name: 'm01b_core_schema' },
{ version: '2026061802', name: 'm01c_async_tasks' }
]);
await assertLegacyCompatibility(pool);
console.log('PASS: second up and verify restored the schema.');