fix: harden financial transaction boundaries

This commit is contained in:
2026-07-18 15:33:41 +08:00
parent 4af7acbeaa
commit 5836b421d8
19 changed files with 514 additions and 314 deletions

View File

@@ -0,0 +1,12 @@
import { Global, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { FinancialOperation } from '../entities/financial-operation.entity';
import { FinancialOperationsService } from './financial-operations.service';
@Global()
@Module({
imports: [TypeOrmModule.forFeature([FinancialOperation])],
providers: [FinancialOperationsService],
exports: [FinancialOperationsService],
})
export class FinancialOperationsModule {}

View File

@@ -0,0 +1,55 @@
import { ConflictException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { FinancialOperation } from '../entities/financial-operation.entity';
@Injectable()
export class FinancialOperationsService {
constructor(
@InjectRepository(FinancialOperation)
private readonly repo: Repository<FinancialOperation>,
) {}
async run<T>(operationId: string | undefined, type: string, work: () => Promise<T>): Promise<T> {
if (!operationId) return work();
if (!/^[\w-]{8,64}$/.test(operationId)) throw new ConflictException('operationId 格式无效');
const existing = await this.repo.findOne({ where: { operationId } });
if (existing) {
if (existing.type !== type) throw new ConflictException('operationId 已用于其他操作');
if (existing.status === 'completed' && existing.resultJson) return JSON.parse(existing.resultJson) as T;
if (existing.status === 'running') throw new ConflictException('该操作正在处理中,请勿重复提交');
}
let operation = existing;
if (!operation) {
try {
operation = await this.repo.save(this.repo.create({ operationId, type, status: 'running' }));
} catch (error) {
const concurrent = await this.repo.findOne({ where: { operationId } });
if (concurrent?.status === 'completed' && concurrent.resultJson) {
return JSON.parse(concurrent.resultJson) as T;
}
throw new ConflictException('该操作正在处理中,请勿重复提交', { cause: error });
}
} else {
operation.status = 'running';
operation.errorMessage = null;
operation.resultJson = null;
await this.repo.save(operation);
}
try {
const result = await work();
operation.status = 'completed';
operation.resultJson = JSON.stringify(result);
await this.repo.save(operation);
return result;
} catch (error) {
operation.status = 'failed';
operation.errorMessage = error instanceof Error ? error.message.slice(0, 500) : '未知错误';
await this.repo.save(operation);
throw error;
}
}
}