优化ai小班长

This commit is contained in:
2026-04-12 22:25:18 +08:00
parent f8d5a3b250
commit ef407a8c6e
7 changed files with 608 additions and 91 deletions
+215
View File
@@ -0,0 +1,215 @@
<?php
/**
* 文件功能:AI小班长资金调度服务
*
* 统一维护 AI小班长的银行存取与阶段性理财里程碑公告,
* 常规场景下仅在手上金币超过 100 万时自动存银行,
* 特殊场景(如买单活动全仓下注)才会动用银行资产。
*/
namespace App\Services;
use App\Events\MessageSent;
use App\Jobs\SaveMessageJob;
use App\Models\BankLog;
use App\Models\User;
use Illuminate\Support\Facades\DB;
/**
* 负责 AI小班长的常规存款、全仓取款与里程碑播报。
*/
class AiFinanceService
{
/**
* AI小班长手上需要保留的最低可用金币。
*/
public const AVAILABLE_GOLD_RESERVE = 1000000;
/**
* 银行存款的阶段性目标。
*
* @var list<int>
*/
private const BANK_MILESTONES = [
10000000,
30000000,
];
/**
* 注入聊天室状态服务,用于里程碑公告广播。
*/
public function __construct(
private readonly ChatStateService $chatState,
) {}
/**
* 计算 AI 当前手上可直接使用的金币。
*/
public function getSpendableGold(User $user): int
{
return (int) ($user->jjb ?? 0);
}
/**
* 计算 AI 当前持有的金币总资产(手上 + 银行)。
*/
public function getTotalGoldAssets(User $user): int
{
return (int) ($user->jjb ?? 0) + (int) ($user->bank_jjb ?? 0);
}
/**
* 判断 AI 当前手上金币是否足够支付本次支出。
*/
public function prepareSpend(User $user, int $spendAmount): bool
{
if ($spendAmount <= 0) {
return true;
}
$user->refresh();
return (int) ($user->jjb ?? 0) >= $spendAmount;
}
/**
* 为全仓支出准备手头金币。
*
* 该模式不会保留手上余额阈值,适用于买单活动等可接受全额下注的场景。
*/
public function prepareAllInSpend(User $user, int $spendAmount): bool
{
if ($spendAmount <= 0) {
return true;
}
return $this->raiseWalletTo($user, $spendAmount);
}
/**
* 100 万以上的富余金币自动转存到银行。
*/
public function bankExcessGold(User $user): void
{
$milestones = DB::transaction(function () use ($user): array {
$lockedUser = User::query()->lockForUpdate()->find($user->id);
if (! $lockedUser) {
return [];
}
$walletGold = (int) ($lockedUser->jjb ?? 0);
if ($walletGold <= self::AVAILABLE_GOLD_RESERVE) {
return [];
}
$bankBefore = (int) ($lockedUser->bank_jjb ?? 0);
$depositAmount = $walletGold - self::AVAILABLE_GOLD_RESERVE;
// 只把超过 100 万的部分转入银行,手上保留不高于 100 万的常规活动资金。
$lockedUser->jjb = self::AVAILABLE_GOLD_RESERVE;
$lockedUser->bank_jjb = $bankBefore + $depositAmount;
$lockedUser->save();
BankLog::create([
'user_id' => $lockedUser->id,
'type' => 'deposit',
'amount' => $depositAmount,
'balance_after' => (int) $lockedUser->bank_jjb,
]);
return array_values(array_filter(
self::BANK_MILESTONES,
fn (int $milestone): bool => $bankBefore < $milestone && (int) $lockedUser->bank_jjb >= $milestone,
));
});
$user->refresh();
foreach ($milestones as $milestone) {
$this->broadcastMilestoneAnnouncement($milestone);
}
}
/**
* 一次性完成 AI 常规理财:仅把超过 100 万的部分转入银行。
*/
public function rebalanceHoldings(User $user): void
{
$this->bankExcessGold($user);
}
/**
* 将手上金币抬升到目标值,必要时从银行自动取款。
*/
private function raiseWalletTo(User $user, int $targetWalletGold): bool
{
$reachedTarget = DB::transaction(function () use ($user, $targetWalletGold): bool {
$lockedUser = User::query()->lockForUpdate()->find($user->id);
if (! $lockedUser) {
return false;
}
$walletGold = (int) ($lockedUser->jjb ?? 0);
if ($walletGold >= $targetWalletGold) {
return true;
}
$bankGold = (int) ($lockedUser->bank_jjb ?? 0);
$withdrawAmount = min($targetWalletGold - $walletGold, $bankGold);
if ($withdrawAmount <= 0) {
return false;
}
// 优先把银行金币提回手上,保证 AI 的即时可用余额尽量回到目标线。
$lockedUser->jjb = $walletGold + $withdrawAmount;
$lockedUser->bank_jjb = $bankGold - $withdrawAmount;
$lockedUser->save();
BankLog::create([
'user_id' => $lockedUser->id,
'type' => 'withdraw',
'amount' => $withdrawAmount,
'balance_after' => (int) $lockedUser->bank_jjb,
]);
return (int) $lockedUser->jjb >= $targetWalletGold;
});
$user->refresh();
return $reachedTarget;
}
/**
* 广播 AI小班长达成银行存款目标的全站公告。
*/
private function broadcastMilestoneAnnouncement(int $milestone): void
{
$roomIds = $this->chatState->getAllActiveRoomIds();
if (empty($roomIds)) {
$roomIds = [1];
}
$milestoneInWan = (int) ($milestone / 10000);
$content = "🏆 🎉 <b>【全站公告】</b> 恭喜 <b>AI小班长</b> 达成理财新高度!<br/>他在银行的存款已成功突破 <span style='color:#e11d48;font-weight:bold;'>{$milestoneInWan}万</span> 金币!💰✨";
foreach ($roomIds as $roomId) {
$message = [
'id' => $this->chatState->nextMessageId($roomId),
'room_id' => $roomId,
'from_user' => '系统传音',
'to_user' => '大家',
'content' => $content,
'is_secret' => false,
'font_color' => '#f59e0b',
'action' => '大声宣告',
'sent_at' => now()->toDateTimeString(),
];
$this->chatState->pushMessage($roomId, $message);
broadcast(new MessageSent($roomId, $message));
SaveMessageJob::dispatch($message);
}
}
}