rwadurian/backend/services/trading-service/src/application/schedulers/c2c-bot.scheduler.ts

121 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { ConfigService } from '@nestjs/config';
import { C2cOrderRepository } from '../../infrastructure/persistence/repositories/c2c-order.repository';
import { C2cBotService } from '../services/c2c-bot.service';
import { RedisService } from '../../infrastructure/redis/redis.service';
/**
* C2C Bot 定时任务
* 定期扫描待处理的卖单并自动购买
*/
@Injectable()
export class C2cBotScheduler implements OnModuleInit {
private readonly logger = new Logger(C2cBotScheduler.name);
private readonly LOCK_KEY = 'c2c:bot:scheduler:lock';
static readonly ENABLED_KEY = 'c2c:bot:enabled';
constructor(
private readonly c2cOrderRepository: C2cOrderRepository,
private readonly c2cBotService: C2cBotService,
private readonly redis: RedisService,
private readonly configService: ConfigService,
) {}
async onModuleInit() {
// 如果 Redis 中没有设置,用环境变量初始化
const existing = await this.redis.get(C2cBotScheduler.ENABLED_KEY);
if (existing === null) {
const envEnabled = this.configService.get<string>('C2C_BOT_ENABLED', 'false');
await this.redis.set(C2cBotScheduler.ENABLED_KEY, envEnabled === 'true' ? 'true' : 'false');
}
const enabled = await this.isEnabled();
this.logger.log(`C2C Bot Scheduler initialized, enabled: ${enabled}`);
if (enabled) {
const isAvailable = await this.c2cBotService.isAvailable();
if (isAvailable) {
const balance = await this.c2cBotService.getHotWalletBalance();
this.logger.log(`Hot wallet balance: ${balance} dUSDT`);
} else {
this.logger.warn('Mining blockchain service not available, Bot will not process orders');
}
}
}
/**
* 检查 Bot 是否启用(读 Redis回退到环境变量
*/
async isEnabled(): Promise<boolean> {
const value = await this.redis.get(C2cBotScheduler.ENABLED_KEY);
if (value === null) {
return this.configService.get<string>('C2C_BOT_ENABLED', 'false') === 'true';
}
return value === 'true';
}
/**
* 每10秒扫描待处理的卖单
*/
@Cron('*/10 * * * * *')
async processPendingSellOrders(): Promise<void> {
if (!(await this.isEnabled())) {
return;
}
const lockValue = await this.redis.acquireLock(this.LOCK_KEY, 30); // 30秒锁留足链上转账时间
if (!lockValue) {
return; // 其他实例正在处理
}
try {
// 每个周期只处理1个订单确保顺序执行
const orders = await this.c2cOrderRepository.findPendingSellOrdersForBot(1);
if (orders.length === 0) {
return;
}
const order = orders[0];
this.logger.log(`[SCHEDULER] Processing order ${order.orderNo} (amount: ${order.totalAmount})`);
try {
const success = await this.c2cBotService.purchaseOrder(order);
this.logger.log(`[SCHEDULER] Order ${order.orderNo}: ${success ? 'success' : 'failed'}`);
} catch (error: any) {
this.logger.error(`[SCHEDULER] Error processing order ${order.orderNo}: ${error.message}`);
}
} catch (error: any) {
this.logger.error(`[SCHEDULER] Error in processPendingSellOrders: ${error.message}`);
} finally {
await this.redis.releaseLock(this.LOCK_KEY, lockValue);
}
}
/**
* 每分钟检查热钱包余额
*/
@Cron('0 * * * * *')
async checkHotWalletBalance(): Promise<void> {
if (!(await this.isEnabled())) {
return;
}
try {
const balance = await this.c2cBotService.getHotWalletBalance();
if (balance) {
// 如果余额低于阈值,记录警告
const threshold = this.configService.get<number>('C2C_BOT_BALANCE_THRESHOLD', 1000);
const balanceNum = parseFloat(balance);
if (balanceNum < threshold) {
this.logger.warn(`[SCHEDULER] Hot wallet balance (${balance}) is below threshold (${threshold})`);
}
}
} catch (error: any) {
this.logger.error(`[SCHEDULER] Error checking hot wallet balance: ${error.message}`);
}
}
}