getDailyCap($source); if ($cap > 0 && ! $this->checkAndIncrRedis($marriage->id, $source, $amount, $cap)) { return; // 已达到每日上限,静默忽略 } } DB::transaction(function () use ($marriage, $amount, $source, $remark) { // 原子加亲密度 $marriage->increment('intimacy', $amount); $newIntimacy = $marriage->fresh()->intimacy; // 更新婚姻等级 $newLevel = self::calcLevel($newIntimacy, $this->config); if ($newLevel !== $marriage->level) { $marriage->update(['level' => $newLevel]); } // 写入日志 MarriageIntimacyLog::create([ 'marriage_id' => $marriage->id, 'amount' => $amount, 'balance_after' => $newIntimacy, 'source' => $source->value, 'remark' => $remark, ]); }); } /** * 每日结婚天数批量加亲密度(ProcessMarriageIntimacy Job 调用)。 * 给所有 married 状态的婚姻对加 intimacy_daily_time 配置的积分。 */ public function dailyBatch(): void { $amount = $this->config->get('intimacy_daily_time', 10); $remark = '每日结婚天数奖励'; Marriage::query() ->where('status', 'married') ->cursor() ->each(function (Marriage $marriage) use ($amount, $remark) { $this->add($marriage, $amount, IntimacySource::DAILY_TIME, $remark, true); }); } /** * 双方同时在线每分钟加亲密度(AutoSaveJob 调用)。 */ public function onlineTick(Marriage $marriage): void { $amount = $this->config->get('intimacy_online_per_min', 1); $this->add($marriage, $amount, IntimacySource::ONLINE_TOGETHER, '双方同时在线'); } /** * 送花触发亲密度(GiftController 调用)。 * 自动判断当前用户是送花方还是收花方,并使用对应加成。 * * @param Marriage $marriage 婚姻关系 * @param int $giverId 送花者 user.id * @param int $flowerQty 送花数量 */ public function onFlowerSent(Marriage $marriage, int $giverId, int $flowerQty = 1): void { // 送花方:send_flower 加成 $sendAmount = $this->config->get('intimacy_send_flower', 1) * $flowerQty; $this->add($marriage, $sendAmount, IntimacySource::SEND_FLOWER, "向伴侣送花×{$flowerQty}"); // 收花方(两人共享同一段婚姻记录,无需区分):recv_flower 加成 $recvAmount = $this->config->get('intimacy_recv_flower', 2) * $flowerQty; $this->add($marriage, $recvAmount, IntimacySource::RECV_FLOWER, "伴侣送花×{$flowerQty}"); } /** * 私聊消息触发亲密度(WhisperController 调用)。 * 使用 Redis 计数,每 2 条触发 1 次加分。 * * @param Marriage $marriage 婚姻关系 */ public function onPrivateChat(Marriage $marriage): void { $redisKey = "marriage:{$marriage->id}:whisper_count:".now()->toDateString(); $count = Redis::incr($redisKey); // 设置 TTL(次日 00:05 过期) if ($count === 1) { Redis::expireAt($redisKey, Carbon::tomorrow()->addMinutes(5)->timestamp); } // 每2条触发1次 if ($count % 2 === 0) { $amount = $this->config->get('intimacy_private_chat', 1); $this->add($marriage, $amount, IntimacySource::PRIVATE_CHAT, '私聊消息'); } } /** * 根据亲密度计算婚姻等级(1-4)。 * * @param int $intimacy 当前亲密度 * @param MarriageConfigService $config 配置服务实例 */ public static function calcLevel(int $intimacy, MarriageConfigService $config): int { if ($intimacy >= $config->get('level4_threshold', 1500)) { return 4; } if ($intimacy >= $config->get('level3_threshold', 600)) { return 3; } if ($intimacy >= $config->get('level2_threshold', 200)) { return 2; } return 1; } /** * 返回等级图标(用于前端展示)。 */ public static function levelIcon(int $level): string { return match ($level) { 2 => '💕', 3 => '💞', 4 => '👑', default => '💑', }; } /** * 返回等级名称。 */ public static function levelName(int $level): string { return match ($level) { 2 => '恩爱夫妻', 3 => '情深意重', 4 => '白头偕老', default => '新婚燕尔', }; } /** * 获取指定来源的每日上限配置值(0=不限)。 */ private function getDailyCap(IntimacySource $source): int { return match ($source) { IntimacySource::ONLINE_TOGETHER => $this->config->get('intimacy_online_daily_cap', 120), IntimacySource::RECV_FLOWER => $this->config->get('intimacy_recv_flower_cap', 40), IntimacySource::SEND_FLOWER => $this->config->get('intimacy_send_flower_cap', 20), IntimacySource::PRIVATE_CHAT => $this->config->get('intimacy_private_chat_cap', 10), default => 0, // 不限 }; } /** * 检查 Redis 每日计数器是否超限,未超限则增加计数。 * 返回 true 表示可以继续加分,false 表示已达上限。 * * @param int $marriageId 婚姻 ID * @param IntimacySource $source 来源 * @param int $amount 本次加分量 * @param int $cap 每日上限 */ private function checkAndIncrRedis(int $marriageId, IntimacySource $source, int $amount, int $cap): bool { $redisKey = "marriage:{$marriageId}:intimacy:{$source->value}:".now()->toDateString(); $current = (int) Redis::get($redisKey); if ($current >= $cap) { return false; // 已达上限 } // 本次可加量(不超过剩余额度) $actualAdd = min($amount, $cap - $current); Redis::incrBy($redisKey, $actualAdd); // 设置 TTL(次日 00:05 过期) if ($current === 0) { Redis::expireAt($redisKey, Carbon::tomorrow()->addMinutes(5)->timestamp); } return true; } }