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('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 { const value = await this.redis.get(C2cBotScheduler.ENABLED_KEY); if (value === null) { return this.configService.get('C2C_BOT_ENABLED', 'false') === 'true'; } return value === 'true'; } /** * 每10秒扫描待处理的卖单 */ @Cron('*/10 * * * * *') async processPendingSellOrders(): Promise { 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 { if (!(await this.isEnabled())) { return; } try { const balance = await this.c2cBotService.getHotWalletBalance(); if (balance) { // 如果余额低于阈值,记录警告 const threshold = this.configService.get('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}`); } } }