优化ai小班长
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user