Files
chatroom/app/Services/AiFinanceService.php
2026-04-12 22:42:32 +08:00

216 lines
6.5 KiB
PHP
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.
<?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);
}
}
}